I'm trying to migrate some JavaScript encryption code to a Dart equivalent but I cannot get the same result.
The JavaScript code is using Stanford JavaScript Crypto Library and the Dart version is using AesGcm from Dart Cryptography 2.5.0 (https://pub.dev/packages/cryptography).
The code in JS is
result = sjcl.codec.hex.fromBits(
sjcl.mode.ccm.encrypt(
new sjcl.cipher.aes(sjcl.codec.hex.toBits("6dbe8f8c87d58e61c2ec29321f42e9ff")), // prf
sjcl.codec.hex.toBits("00626c742e332e3132397649356b744455415443"), // plaintext
sjcl.codec.hex.toBits("101112131415161718191A1B"), // iv
sjcl.codec.hex.toBits("6465764944"), // adata
32));
The Dart I try to implement is
final encrypted = await AesGcm.with256bits().encrypt(
secretKey: SecretKey(utf8.encode("6dbe8f8c87d58e61c2ec29321f42e9ff")), // prf ?
utf8.encode("00626c742e332e3132397649356b744455415443"), // plaintext (device_new_id?)
nonce: utf8.encode("101112131415161718191A1B"), // iv
aad: utf8.encode("6465764944"), // adata
);
// Convert encrypted bytes to hex
final result= bytesToHex( Uint8List.fromList(encrypted.cipherText));
The result differs but I cannot see why ?
Also tried with hex decode:
I've tried this:
final encrypted = await AesGcm.with256bits().encrypt(
secretKey: SecretKey(hex.decode("6dbe8f8c87d58e61c2ec29321f42e9ff")), // prf ?
hex.decode("00626c742e332e3132397649356b744455415443"), // plaintext (device_new_id?)
nonce: hex.decode("101112131415161718191A1B"), // iv
aad: hex.decode("6465764944"), // adata
);
Gives an exception: "Invalid argument (secretKeyData): Expected 32 bytes, got 16 bytes: Instance of 'SecretKeyData'"
Hope someone can point me in the right direction ?
Best regards Martin
JavaScript and Dart code use different modes, the JavaScript code applies CCM, the Dart code GCM. Although both modes are authenticated encryption and based on CTR the algorithms are different.
You need a library on the Dart side that supports CCM. One option is PointyCastle. A possible implementation is:
import 'package:pointycastle/export.dart';
import 'dart:typed_data';
import 'package:convert/convert.dart';
...
Uint8List key = Uint8List.fromList(hex.decode("6dbe8f8c87d58e61c2ec29321f42e9ff"));
Uint8List nonce = Uint8List.fromList(hex.decode("101112131415161718191A1B"));
Uint8List aad = Uint8List.fromList(hex.decode("6465764944"));
Uint8List plaintext = Uint8List.fromList(hex.decode("00626c742e332e3132397649356b744455415443"));
// Encrypt with AES/CCM
CCMBlockCipher encrypter = CCMBlockCipher(AESEngine());
AEADParameters paramsEnc = AEADParameters(KeyParameter(key), 32, nonce, aad);
encrypter.init(true, paramsEnc);
Uint8List ciphertextTag = encrypter.process(plaintext);
print(hex.encode(ciphertextTag)); // a2bbe65fcc7d13a8aeef2309055c3e2d24cd2f07bd2265cc - the last 4 bytes are the tag
// Decrypt with AES/CCM
CCMBlockCipher decrypter = CCMBlockCipher(AESEngine());
AEADParameters paramsDec = AEADParameters(KeyParameter(key), 32, nonce, aad);
decrypter.init(false, paramsDec);
Uint8List decrypted = decrypter.process(ciphertextTag);
print(hex.encode(decrypted)); // 00626c742e332e3132397649356b744455415443
Like sjcl/JavaScript, PointyCastle/Dart concatenates ciphertext and tag: ciphertext|tag
, i.e. the last 4 bytes (corresponding to the explicitly set tag size of 32 bits) are the tag.
Test: The JavaScript code produces the same ciphertext and tag (for the same input data):
var result = sjcl.codec.hex.fromBits(
sjcl.mode.ccm.encrypt(
new sjcl.cipher.aes(sjcl.codec.hex.toBits("6dbe8f8c87d58e61c2ec29321f42e9ff")), // prf
sjcl.codec.hex.toBits("00626c742e332e3132397649356b744455415443"), // plaintext
sjcl.codec.hex.toBits("101112131415161718191A1B"), // iv
sjcl.codec.hex.toBits("6465764944"), // adata
32))
console.log(result) // a2bbe65fcc7d13a8aeef2309055c3e2d24cd2f07bd2265cc
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/sjcl@1.0.8/sjcl.min.js"></script>
Security:
Note that for CCM a minimum tag size of 8 bytes is recommended, s. RFC 3610, sec. 4. Rationale.
Also, for CCM, using a static IV/nonce is a serious vulnerability (with fixed key). Instead, a random IV/nonce should be generated during encryption, which is passed together with the ciphertext to the decrypting side, usually concatenated (the IV/nonce is not a secret).