首页 > 解决方案 > 禁用加密填充

问题描述

如果我使用此代码:

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 禁用填充吗?如果不是,那么获得相同较短输出的好方法是什么?

标签: javascriptencryptioncryptographywebcrypto-api

解决方案


WebCrypto 默认情况下在 AES-CBC PKCS7 填充的上下文中使用(这里),据我所知,它不能被禁用(s.也是文档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==作为输出提供。

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


当然有一些 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>


推荐阅读