禁用加密填充

Disable crypto padding

如果我使用此代码:

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))))
);

我得到这个结果:

q6BAetimbeLcdlSC7GoBbtrh/HM4xs3t1+BzEYxdEIk=

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

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

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

echo openssl_encrypt(
   'January February',
   'aes-128-cbc',
   'GHIJKLMNOPQRSTUV',
   OPENSSL_ZERO_PADDING,
   '0123456789ABCDEF'
);

结果:

q6BAetimbeLcdlSC7GoBbg==

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

WebCrypto 在 AES-CBC 上下文中使用 PKCS7 padding by default (), which as far as I know cannot be disabled (s. also the documentation of 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', 'aes-128-cbc', 'GHIJKLMNOPQRSTUV', OPENSSL_RAW_DATA, '0123456789ABCDEF'), 0, -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, 
    key,
    {
        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, key, iv] = ['January February', 'GHIJKLMNOPQRSTUV', '0123456789ABCDEF'];
// default is padding, omit last method if needed
let cipher = crypto.createCipheriv('aes-128-cbc', key, 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, key, 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, key, iv, Padding.PKCS7);
let ct = btoa(String.fromCharCode(...cipher.encrypt(pt)));
console.log(ct);