We are porting an app from native Android/iOS to Flutter. We are using AES symmetric encryption in native apps and they are working fine with our device.
Following is the flutter code where we have tried to replicate java implementation.
Here we send client(app) public key and client nonce (random 16 bit) to the device. Device creates an encrypted packet with device public key, client public key and mac id, and send it to app after encrypting it. We decrypt the received packet on app and create a similar packet on our side and we match both of these packets.
In our case, both of them are matching in Flutter (Which suggest that we have correctly completed the encryption process). The problem is, after this process when we try to send next command to device by encrypting the data, device is not able to understand it and replies with junk data.
import 'dart:math';
import 'dart:typed_data';
import 'package:asn1lib/asn1lib.dart';
import 'package:pc_steelcrypt/export.dart';
import 'package:steel_crypt/steel_crypt.dart';
import 'package:collection/collection.dart';
Future<void> startEncryption() async {
const String SALT_AES_KEY = 'AES-KEY';
const String SALT_AES_IV = 'AES-IV';
final Uint8List serverResponse = await sendUnEncryptedCommand(getFirstMessagePayload());
final Uint8List macId = getMacId(serverResponse);
final Uint8List saltNonceServer = getSaltNounceServer(serverResponse);
final Uint8List serverCertificate = getCertificate(serverResponse);
//Parsing server certificate
final ASN1Parser p = ASN1Parser(serverCertificate);
final ASN1Sequence signedCert = p.nextObject() as ASN1Sequence;
final ASN1Sequence cert = signedCert.elements[0] as ASN1Sequence;
final ASN1Sequence pubKeyElement = cert.elements[6] as ASN1Sequence;
final ASN1BitString pubKeyBits = pubKeyElement.elements[1] as ASN1BitString;
print('pubKeyBits : $pubKeyBits');
//TODO: Need to authenticate certificate too
//Client keys generation:
final ECCurve_secp256r1 secp256r1 = ECCurve_secp256r1();
final ECPoint Q =
secp256r1.curve.decodePoint(pubKeyBits.contentBytes().toList());
final ECPublicKey serverPublicKey = ECPublicKey(Q, secp256r1);
final ECCurve_secp256r1 _nistp256 = ECCurve_secp256r1();
final ECKeyGeneratorParameters ecKeyGeneratorParameters =
ECKeyGeneratorParameters(_nistp256);
final SecureRandom sr = getSecureRandom();
final ParametersWithRandom parametersWithRandom =
ParametersWithRandom(ecKeyGeneratorParameters, sr);
final ECKeyGenerator ecKeyGenerator = ECKeyGenerator();
ecKeyGenerator.init(parametersWithRandom);
final _clientKeyPair = ecKeyGenerator.generateKeyPair();
final SecureRandom sr2 = getSecureRandom();
final Uint8List clientNonce = sr2.nextBytes(16);
final clientPubKeyRaw = getCertificateRaw(_clientKeyPair.publicKey as ECPublicKey);
final Uint8List msg = Uint8List(80);
List.copyRange(msg, 0, clientPubKeyRaw, 0, 64);
List.copyRange(msg, 64, clientNonce, 0, 16);
final Uint8List signedData = await sendUnEncryptedCommand(msg);
final secret = serverPublicKey.Q * (_clientKeyPair.privateKey as ECPrivateKey).d;
Uint8List sharedSecret = secret.getEncoded();
if (sharedSecret.length == 33) {
sharedSecret = sharedSecret.sublist(1);
}
final Uint8List aesKey = generateAes128BitKeyForBle(sharedSecret,Uint8List.fromList(SALT_AES_KEY.codeUnits), saltNonceServer, clientNonce);
final Uint8List aesIv = generateAes128BitKeyForBle(sharedSecret,Uint8List.fromList(SALT_AES_IV.codeUnits), saltNonceServer, clientNonce);
final AesCryptRaw aesEncrypter = AesCryptRaw(padding: PaddingAES.none, key: aesKey);
final Uint8List serverPubKeyRaw = getCertificateRaw(serverPublicKey);
final int clientToBeSignedLength = serverPubKeyRaw.length + clientPubKeyRaw.length + macId.length;
final Uint8List clientToBeSigned = Uint8List(clientToBeSignedLength);
List.copyRange(clientToBeSigned, 0, serverPubKeyRaw, 0,serverPubKeyRaw.length);
List.copyRange(clientToBeSigned, serverPubKeyRaw.length, clientPubKeyRaw, 0,clientPubKeyRaw.length);
List.copyRange(clientToBeSigned, serverPubKeyRaw.length + clientPubKeyRaw.length, macId, 0,macId.length);
final Digest md = SHA256Digest();
md.update(clientToBeSigned, 0, clientToBeSigned.length);
final Uint8List clientHash = Uint8List(md.digestSize);
md.doFinal(clientHash, 0);
final Uint8List serverHash = aesEncrypter.ctr.decrypt(enc: signedData, iv: aesIv);
final Function deepEq = DeepCollectionEquality().equals;
final bool result = deepEq(serverHash, clientHash) as bool;
if (result) {
print('Encryption success');
//sending encrypted command for the first time to device. Encrypting using aesEncrypter
sendEncryptedCommand(aesEncrypter, Uint8List.fromList([2,2]));
} else {
print('Encryption failure');
}
}
Uint8List getCertificateRaw(ECPublicKey publicKey){
final publicKeyRaw = Uint8List(64);
Uint8List result = publicKey.Q.getEncoded(false);
result = result.length == 65 ? result.sublist(1) : result;
List.copyRange(publicKeyRaw, 0, result, 0, result.length);
return publicKeyRaw;
}
Uint8List generateAes128BitKeyForBle(Uint8List sharedSecret,
Uint8List salt, Uint8List serverNonce, Uint8List clientNonce) {
final Digest digester = SHA256Digest();
digester.update(sharedSecret, 0, sharedSecret.length);
digester.update(salt, 0, salt.length);
digester.update(clientNonce, 0, clientNonce.length);
digester.update(serverNonce, 0, serverNonce.length);
final Uint8List outBuffer = Uint8List(digester.digestSize);
digester.doFinal(outBuffer, 0);
if (outBuffer.length <= 16) {
//log('Encrypter' ,'digest length is smaller than 16');
return outBuffer;
} else {
final List<int> aesKey = List<int>(16);
List.copyRange(aesKey, 0, outBuffer.toList(), 0, 16);
return Uint8List.fromList(aesKey);
}
}
SecureRandom getSecureRandom() {
final secureRandom = FortunaRandom();
final random = Random.secure();
final List<int> seeds = [];
for (int i = 0; i < 32; i++) {
seeds.add(random.nextInt(255));
}
secureRandom.seed(KeyParameter(Uint8List.fromList(seeds)));
return secureRandom;
}
Following is the code snippet from Java/Android used for encryption.
public class AesCtrCipher {
private Cipher mCipher;
public void init(byte[] rawKey, byte[] inputVector) {
if (mCipher == null) {
SecretKeySpec aesKeySpec = new SecretKeySpec(rawKey, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(inputVector);
try {
mCipher = Cipher.getInstance("AES/CTR/NoPadding");
mCipher.init(Cipher.ENCRYPT_MODE, aesKeySpec, ivSpec);
} catch (Exception e) {
throw new CustomException("Encryption exception : " + e.getMessage(), Encryption);
}
}
}
public byte[] crypt(byte[] data) {
try {
return mCipher.update(data);
} catch (Exception e) {
throw new CustomException("Encryption exception : " + e.getMessage(), Encryption);
}
}
}
Code to generate keys in Java:
class EncryptionKeyPair {
private static ECParameterSpec nistp256 = new ECParameterSpec(
new EllipticCurve(
new ECFieldFp(new BigInteger
("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
16)),
new BigInteger
("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
16),
new BigInteger
("5ac635d8aa3a93e7bb2ebdb7676RE86bc651d06b45c54c0f63bce3c3e27d2604b",
16)),
new ECPoint(new BigInteger
("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A18655D898C296", 16),
new BigInteger
("3DE342FAFE2A7F9B4AA6EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
16)),
new BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFECE4FAAFA7179E94F4A9CAB2FC432543", 16),
1);
public ECPrivateKey mPrivate;
public ECPublicKey mPublic;
public void generateKey() throws IOException {
KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(nistp256);
KeyPair pair = kpg.generateKeyPair();
mPrivate = (ECPrivateKey) pair.getPrivate();
mPublic = (ECPublicKey) pair.getPublic();
} catch (NoSuchAlgorithmException e) {
throw new IOException("No DH keypair generator", e);
} catch (InvalidAlgorithmParameterException e) {
throw new IOException("Invalid DH parameters", e);
}
}
}
Generating shared secret in Java
KeyAgreement ka;
try {
ka = KeyAgreement.getInstance("ECDH");
ka.init((ECPrivateKey) keyPair.mPrivate);
ka.doPhase(certificate.getPublicKey(), true);
return ka.generateSecret();
} catch (Exception e) {
e.printStackTrace();
}
Please let me know if any other information is required? Can somebody point out what wrong we are doing?