I have a requirement where a string will be encrypted from flutter web app using Dart. The encrypted string goes to server and the same will be consumed by iOS Swift.
Dart file
String encryptString(Uint8List key) {
try {
final encryptionKey = Key(key);
final iv = IV.fromLength(16); // 16 bytes IV for AES-GCM
final macValue = utf8.encode("");
final encrypter = Encrypter(AES(encryptionKey, mode: AESMode.gcm));
final encrypted = encrypter.encrypt(
this,
iv: iv,
associatedData: macValue,
);
final concatenatedBytes = Uint8List.fromList([...iv.bytes, ...encrypted.bytes, ...macValue]);
final base64Encoded = base64.encode(concatenatedBytes);
return base64Encoded;
} catch (e) {
print('Error while encrypting: $e');
return '';
}
}
However, the encrypted text I am not able to decrypt in Swift. I always get CryptoKit.CryptoKitError.authenticationFailure
Swift code
func decrypt(withKey key: SymmetricKey) throws -> String {
guard let encryptedData = Data(base64Encoded: self) else { throw EncryptionError.invalidData }
// Open the sealed box using the encryption key
do {
let sealedBox = try AES.GCM.SealedBox(combined: encryptedData)
// Decrypt the data
let decryptedData = try AES.GCM.open(sealedBox, using: key)
// Convert decrypted data to string
guard let decryptedString = String(data: decryptedData, encoding: .utf8) else {
throw EncryptionError.decryptionFailed
}
return decryptedString
} catch {
print(error.localizedDescription)
throw EncryptionError.invalidData
}
}
I tried the above snippets. Seems not to work, Please help 🙁
2
Answers
Cryptographic schemes are all incompatible to each other. You’re using the default "SIC" scheme (AES in counter mode) on Dart and then you’re trying to decrypt using another scheme called GCM in Swift. That’s obviously not going to work, and the Dart library doesn’t seem to support authenticated ciphers such as GCM. Those are needed otherwise you’re prone to plaintext oracle attacks as well as having no message integrity or authentication (which, as you might imagine, is bad).
You could try Fernet which is included in the "encrypt" library of Dart. It at least adds a HMAC tag for authentication, which basically makes it an authenticated cipher. A quick look showed this answer containing code in swift.
Note that this is not a recommendation for the Dart or Swift code; I haven’t code reviewed it in any way. But without any crypto knowledge you could go worse than using a "sealed box" such as NaCL, Fernet or such. I did perform a protocol review for Fernet a long time ago, for what that’s worth.
The Dart library raises a lot of red flags. It has great scores, but it doesn’t officially list any specifications and it doesn’t seem not notice that AES-SIC and AES-CTR are actually the same mode, which shows that the author and the community are probably rather inexperienced.
Although GCM is not listed as a supported operation mode in the encrypt package documentation (Usage section), as you have already found out yourself, newer versions support GCM (see issue#259 and GCM-example), so this package can be used.
Note that encrypt is only a high level wrapper for some PointyCastle functionalities, and that PointyCastle could be used directly as well.
Apart from the incorrect operation mode (see other answer), both codes are still incompatible even after changing the Dart code to GCM mode. This is due to the nonce size of 16 bytes applied.
For GCM, a 12 byte nonce is recommended, which should be adhered to for performance and compatibility reasons.
The Swift library supports a 16 bytes nonce, but not in the
combined
representation, which is used in the current code and which may only be used for a 12 bytes nonce.Instead, the nonce, ciphertext and tag must be specified explicitly, i.e. they must be separated in the Swift code. Since the encrypt package uses the default tag length of 16 bytes and automatically appends the tag to the ciphertext, the data has the following structure:
nonce (16 bytes) | ciphertext | tag (16 bytes)
, so that the parts can be separated using the known lengths of tag and nonce, e.g:Keep in mind, in the case of a 12 bytes nonce, the
combined
representation can be used. In this case, the data must have the following structure:nonce (12 bytes) | ciphertext | tag (16 bytes)
.Note that the name
macValue
in the Dart code is misleading. MAC is used as a synonym for the authentication tag. However, what you refer to asmacValue
is not the authentication tag but the additional authenticated data (AAD). This is data that does not need to be encrypted but should nevertheless be authenticated in order to ensure its integrity and authenticity.In contrast, the authentication tag (or MAC) is generated automatically during encryption (and, depending on the library, either appended to the ciphertext or returned separately). This tag is used during decryption to check the integrity and authenticity of the data.
The current code uses an empty string for the AAD, i.e. effectively no AAD, so that consideration in the Swift code is not necessary. However, if AAD were to be used: In the case of the
combined
representation, the AAD must not be concatenated or must be removed before being passed toAES.GCM.SealedBox()
. The AAD are explicitly specified inAES.GCM.open()
(seeauthenticating
).Incidentally, if the AAD are concatenated as in the current code (which is rather unusual), their length must be known to the decrypting side (at least if the ciphertext length is not known) so that separation is possible.