禁用加密填充

问题描述

如果我使用此代码

<div class="iw ajw">
  text before
  <span translate="no" class="hb">to <span email="[email protected]" name="Mark" data-hovercard-id="[email protected]" class="g2" data-hovercard-owner-id="119">Mark</span>
  </span>
  text after
</div>

我得到了这个结果:

let enc = new TextEncoder();
let data = enc.encode('January February');

let algorithm = {
   name: 'AES-CBC',iv: enc.encode('0123456789ABCDEF')
};

crypto.subtle.importKey(
   'raw',enc.encode('GHIJKLMnopQRSTUV'),'AES-CBC',true,['encrypt']
).then(
   key => crypto.subtle.encrypt(algorithm,key,data)
).then(
   ct => console.log(btoa(String.fromCharCode(...new Uint8Array(ct))))
);

如果我使用这个 PHP 代码,我得到相同的结果:

q6BAetimbeLcdlSC7GoBbtrh/HM4xs3t1+BzEYxdEIk=

但是 PHP 可以选择禁用填充:

echo openssl_encrypt(
   'January February','aes-128-cbc','GHIJKLMnopQRSTUV',iv: '0123456789ABCDEF'
);

结果:

echo openssl_encrypt(
   'January February',OPENSSL_ZERO_PADDING,'0123456789ABCDEF'
);

可以使用 JavaScript 禁用填充吗?如果没有,什么是获得 同样较短的输出

解决方法

WebCrypto 默认在 AES-CBC PKCS7 padding 的上下文中使用(here),据我所知不能禁用(也是 SubtleCrypto.encrypt() 的文档)。

只有在明文是块大小的整数倍(AES 为 16 字节)时,才可以使用 AES-CBC 进行无填充加密,如您的示例所示。在这种情况下,PKCS7 会附加一个带有填充字节(都是 0x10)的完整块,即在密文中只需要删除最后一个块:

//
// Your code (implicit base64 encoding)
//
$enc = openssl_encrypt('January February','aes-128-cbc','GHIJKLMNOPQRSTUV',OPENSSL_ZERO_PADDING,'0123456789ABCDEF');
print($enc . PHP_EOL);

//
// Remove last block (explicit Base64 encoding required,since the last block of the ACTUAL ciphertext must be removed)
//
$enc = base64_encode(substr(openssl_encrypt('January February',OPENSSL_RAW_DATA,'0123456789ABCDEF'),-16));
print($enc . PHP_EOL);

let enc = new TextEncoder();
let data = enc.encode('January February');

let algorithm = {
   name: 'AES-CBC',iv: enc.encode('0123456789ABCDEF')
};

crypto.subtle.importKey(
   'raw',enc.encode('GHIJKLMNOPQRSTUV'),'AES-CBC',true,['encrypt']
).then(
   key => crypto.subtle.encrypt(algorithm,key,data)
).then(     
   ct => console.log(btoa(String.fromCharCode(...new Uint8Array(ct).slice(0,-16)))) // reomve last block
);

两个代码段都提供 q6BAetimbeLcdlSC7GoBbg== 作为输出。

应该注意的是,解密成本更高,因为必须添加加密填充字节(如果解密后没有有效的 PKCS7 填充,则会抛出 DOMException)。


当然有一些 JavaScript 库比低级 WebCrypto API 更舒适,并且还支持不同的填充以及禁用填充,例如CryptoJS

var data = 'January February';
var key = CryptoJS.enc.Utf8.parse('GHIJKLMNOPQRSTUV');
var iv = CryptoJS.enc.Utf8.parse('0123456789ABCDEF');

var encrypted = CryptoJS.AES.encrypt(
    data,{
        iv: iv,padding: CryptoJS.pad.NoPadding
    }
);

console.log(encrypted.toString());
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

,

其他答案很好,但作为替代方案,您可以使用内置节点 crypto API:

let crypto = require('crypto');
let [pt,iv] = ['January February','0123456789ABCDEF'];
// default is padding,omit last method if needed
let cipher = crypto.createCipheriv('aes-128-cbc',iv).setAutoPadding(false);
let ct = cipher.update(pt,'utf8','base64') + cipher.final('base64');
console.log(ct);

这是一个使用 Deno crypto 库的示例:

import { Aes } from 'http://deno.land/x/crypto/aes.ts';
import { Cbc,Padding } from 'http://deno.land/x/crypto/block-modes.ts';
let te = new TextEncoder;

let [pt,iv] = [
   te.encode('January February'),te.encode('GHIJKLMNOPQRSTUV'),te.encode('0123456789ABCDEF')
];

// default is no padding,omit last argument if needed
let cipher = new Cbc(Aes,iv,Padding.PKCS7);
let ct = btoa(String.fromCharCode(...cipher.encrypt(pt)));
console.log(ct);