Skip to content

SecretBoxAuthenticationError SecretBox has wrong HMAC #147

Description

@stevenspiel

I know other issues have been opened similar to this one, and the response is that it should be fixed in 2.2.0, however, users are still experiencing it on 2.2.0 and 2.5.0.

Here is a breakpoint in aes_gcm.dart where the exception is getting raised.

Screenshot 2023-05-28 at 9 24 54 PM

The mac and calculatedMac are different, which is throwing the error. What else could be causing this issue?

Activity

  1. stevenspiel commented on May 30, 2023

    @stevenspiel
    Author

    Update: when I remove the mac comparison (lines 133-135), the decryption works as expected.

  2. akecht commented on May 31, 2023

    @akecht

    Can you provide a minimal example of your encryption and decryption code where the issue occurs?

  3. stevenspiel commented on May 31, 2023

    @stevenspiel
    Author

    here's the decryption. Working on getting the encryption example:

    import 'dart:convert';
    
    import 'package:cryptography/cryptography.dart';
    
    void main() async {
      final _symmetricAlgorithm = AesGcm.with256bits();
      final hkdf = Hkdf(
        hmac: Cryptography.instance.hmac(Sha256()),
        outputLength: 32,
      );
    
      final key = 'ryUDXJiLUCOboYbmuG87nTIi+to1IPgJy6/hQZmr6A4=';
      final masterKey = SecretKeyData(base64Decode(key));
      final nonce = utf8.encode('storage_context');
    
      final encryptedWordsMap = {
        "iv": "ZfSMjhYIGgA9Ncti",
        "ciphertext": "unqsCRdKhNYrPlqpY+9uFcZTxIWUxEP7c6fb9lagcOU=",
        "mac": "bw6FRVKS/VMZfx8ABb4v2Q==",
      };
      final encryptedWordsBox = SecretBox(
        base64Decode(encryptedWordsMap['ciphertext'] as String),
        nonce: base64Decode(encryptedWordsMap['iv'] as String),
        mac: Mac(base64Decode(encryptedWordsMap['mac'] as String)),
      );
      final plaintext = await _symmetricAlgorithm.decrypt(
        encryptedWordsBox,
        secretKey: await hkdf.deriveKey(secretKey: masterKey, nonce: nonce),
      );
    
      print(plaintext);
    }
  4. fedper95 commented on May 31, 2023

    @fedper95

    I'm experiencing the same issue.

    I have debugged aes_gcm.dart in the lines pointed by @stevenspiel and the mac I get from the SecretBox when encrypting with encryptString and the bytes are indeed different. Also verified the mac received by the method (not the calculated one) is the same mac that I got from my SecretBox.

    Flutter version: 3.7.8
    Packages:
    cryptography: ^2.5.0
    cryptography_flutter: ^2.3.0

    Here is my flutter doctor:

    Doctor summary (to see all details, run flutter doctor -v):
    [✓] Flutter (Channel stable, 3.7.8, on macOS 13.4 22F66 darwin-arm64, locale en-UY)
    [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
    [✓] Xcode - develop for iOS and macOS (Xcode 14.3)
    [✓] Chrome - develop for the web
    [✓] Android Studio (version 2022.2)
    [✓] VS Code (version 1.78.2)
    [✓] Connected device (3 available)
    [✓] HTTP Host Availability

    • No issues found!


  5. fedper95 commented on May 31, 2023

    @fedper95

    Should we follow any steps in order to create a SecretBox and Mac from bytes?

    I'm doing it this way (also tried without converting the bytes list to UInt8List)

    final mac = Mac(Uint8List.fromList(encryptedMessage.mac));
    
        return SecretBox(
          encryptedMessage.message,
          nonce: encryptedMessage.nonce,
          mac: mac,
        );
    
  6. fedper95 commented on May 31, 2023

    @fedper95

    Mac bytes from the SecretBox obtained from encryptString method: [212,112,100,80,198,148,209,212,53,47,209,41,92,167,20,129]

    Mac bytes recreated by the code I shared in my last comment:
    [212,112,100,80,198,148,209,212,53,47,209,41,92,167,20,129]

    Mac received by decryptSync:
    [212,112,100,80,198,148,209,212,53,47,209,41,92,167,20,129]

    Calculated mac:
    [173,138,245,156,141,162,54,188,244,172,26,3,99,100,127,48]

  7. fedper95 commented on May 31, 2023

    @fedper95

    Even when passing the same SecretBox directly from encrypt to decrypt, I get the same scenario.

    If it helps, I'm using X25519 to create the keys with the following code:

    final keys = await _keyExchange.newKeyPair();
    final publicKey = await keys.extractPublicKey();
    final secretKey = await keys.extractPrivateKeyBytes();
    
  8. fedper95 commented on May 31, 2023

    @fedper95

    I tried decrypting in the same method I encrypted (the only difference seems to be the sharedKey) and it worked.

    Could it be the error gets thrown when the key is wrong?

    This is the working code:

    final sharedSecretKey = await _getSharedSecret(
          remotePublicKeyBytes: receiverPublicKeyBytes,
          userPublicKeyBytes: userPublicKeyBytes,
        );
    
        final encryptedData = await _cipher.encryptString(
          text,
          secretKey: sharedSecretKey,
        );
    
        final data = encryptedData.concatenation();
    
        final newData = SecretBox(
          encryptedData.cipherText,
          nonce: encryptedData.nonce,
          mac: encryptedData.mac,
        );
        final decrypted = await _cipher.decryptString(
          newData,
          secretKey: sharedSecretKey,
        );
    
        return data.toList();
    

    Doing the same but trying to recover the sharedSecret from the other end (message receiver user) throws the MAC error

  9. akecht commented on May 31, 2023

    @akecht

    @stevenspiel your encrypted data also fails to decrypt when I tested with C#/NSec which probably means something went wrong when encrypting.

    @fedper95 yes, an incorrect key will result in a failed decryption and different mac. In your example you're using two public keys for the shared secret, I don't know what exactly you do in _getSharedSecret, but you need a private key for the key agreement.

    X25519 example code

    import 'package:cryptography/cryptography.dart';
    
    Future<void> main() async {
      final algorithm = X25519();
    
      // Alice chooses her key pair
      final aliceKeyPair = await algorithm.newKeyPair();
    
      // Alice knows Bob's public key
      final bobKeyPair = await algorithm.newKeyPair();
      final bobPublicKey = await bobKeyPair.extractPublicKey();
    
      // Alice calculates the shared secret.
      final sharedSecret = await algorithm.sharedSecretKey(
        keyPair: aliceKeyPair,
        remotePublicKey: bobPublicKey,
      );
      final sharedSecretBytes = await sharedSecret.extractBytes();
      print('Shared secret: $sharedSecretBytes');
    }

  10. stevenspiel commented on May 31, 2023

    @stevenspiel
    Author

    The encryption was done using 2.0.5. I see some comments about 2.2.0 fixing the issue

    #71 (comment)
    #85 (comment)

    1. What exactly changed in 2.2.0 that fixed this?
    2. Is there any way to recover faulty encryption done with 2.0.5 (other than bypassing the hmac check)?
  11. akecht commented on Jun 1, 2023

    @akecht

    I don't know what changed in 2.2.0 but if the mac was calculated incorrectly there is not much you can do other than bypassing the check somehow I think.

    If it really is just the mac that is incorrect and you can recover the plaintext by bypassing the hmac check you could also just use Aes-Ctr with a modified nonce and an empty hmac (see here for more details https://stackoverflow.com/questions/49228671/aes-gcm-decryption-bypassing-authentication-in-java/49244840#49244840) - but please note: just because it decrypts does not necessarily mean you get the correct plaintext - you also don't know if your key was correct or not and you should really know what you are doing if you go this route.
    I quickly tried it out yesterday, so if you need an example of that I can post one later.

    If there was however also an error with the ciphertext and you can't decrypt it even with old versions I fear the data is likely lost.
    Please note, I'm not an expert on this and might be wrong.

  12. Nialixus commented on Oct 19, 2023

    @Nialixus

    This issue reoccured in ^2.7.0

  13. 5eeman commented on Aug 5, 2024

    @5eeman

    Yep, same for me

  14. kevin4dhd commented on May 23, 2025

    @kevin4dhd

    Any solution?

  15. 5eeman commented on May 23, 2025

    @5eeman

    @kevin4dhd
    I've somehow came through the problem.
    Can't share the full code, but here is a sample file with utils and configured variable I'm using for AES GCM.
    Don't know if it will be helpful for u.

    import 'dart:convert';
    
    import 'package:cryptography/cryptography.dart';
    
    const nonceLength = 12;
    final aesGcm = AesGcm.with256bits(nonceLength: nonceLength);
    final macLength = aesGcm.macAlgorithm.macLength;
    const base64UrlCodec = Base64Codec();
    
    extension SecretBoxExtension on SecretBox {
      String toBase64Url() {
        // 12 bytes nonce + encrypted key + 16 bytes mac
        return base64UrlEncode(nonce + cipherText + mac.bytes);
      }
    
      String toHex() {
        // 12 bytes nonce + encrypted key + 16 bytes mac
        return (nonce + cipherText + mac.bytes).toHex();
      }
    }
    
    extension StringAesExtension on String {
      SecretBox secretBoxFromBase64Url() {
        final bytes = base64Url.decode(base64UrlCodec.normalize(this));
        final nonce = bytes.sublist(0, nonceLength);
        final cipherText = bytes.sublist(nonceLength, bytes.length - macLength);
        final mac = Mac(bytes.sublist(bytes.length - macLength));
    
        return SecretBox(cipherText, nonce: nonce, mac: mac);
      }
    
      SecretBox secretBoxFromHex() {
        final bytes = HexUtils.hexToBytes(this);
        final nonce = bytes.sublist(0, nonceLength);
        final cipherText = bytes.sublist(nonceLength, bytes.length - macLength);
        final mac = Mac(bytes.sublist(bytes.length - macLength));
    
        return SecretBox(cipherText, nonce: nonce, mac: mac);
      }
    }
    
    
  16. quangpc commented on Jul 24, 2025

    @quangpc

    hi @5eeman
    can you tell me what is the problem that needs to be fixed?
    my code is quite similar to your sample code, but i don't know what is missing.

    /// Encrypt plaintext → base64(nonce + ciphertext + mac)
      Future<String> encrypt(String plainText) async {
        final nonce = _cipher.newNonce(); // 12 bytes nonce
        final secretBox = await _cipher.encrypt(
          utf8.encode(plainText),
          secretKey: _secretKey,
          nonce: nonce,
        );
    
        final combined = Uint8List.fromList([
          ...nonce,
          ...secretBox.cipherText,
          ...secretBox.mac.bytes,
        ]);
    
        return base64Encode(combined);
      }
    
      /// Decrypt base64(nonce + ciphertext + mac) → plaintext
      Future<String> decrypt(String base64Data) async {
        final combined = base64Decode(base64Data);
        final nonce = combined.sublist(0, 12);
        final cipherTextLength = combined.length - 12 - 16;
        final cipherText = combined.sublist(12, 12 + cipherTextLength);
        final macBytes = combined.sublist(12 + cipherTextLength);
    
        final secretBox = SecretBox(
          cipherText,
          nonce: nonce,
          mac: Mac(macBytes),
        );
    
        final clearText = await _cipher.decrypt(secretBox, secretKey: _secretKey);
        return utf8.decode(clearText);
      }
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions