Aes 加密 Galois/Counter 模式尝试登录服务器端出现无效凭证问题,

问题描述 投票:0回答:1

下面是dart Aes加密逻辑的代码

  static String encryptAESGCM(String plaintext, String key) {
    try {
      final keyBytes = Uint8List.fromList(utf8.encode(key));
      // SecureRandom secureRandom = getSecureRandom();
      // Uint8List data = secureRandom.nextBytes(12);
        Uint8List data = generateSecureRandomData(12);
      // Uint8List data = generateNonSecureRandomData(12);
      final cipher = GCMBlockCipher(AESEngine());
      final params = AEADParameters(KeyParameter(keyBytes), 128, data, Uint8List(0));
      cipher.init(true, params); // Initialize for encryption

      final plaintextBytes = utf8.encode(plaintext);
      final ciphertextBytes = cipher.process(Uint8List.fromList(plaintextBytes)); // Process all data at once

      return base64.encode(ciphertextBytes); // Base64 encode for transmission
    } catch (error) {
      // Handle encryption errors gracefully
      print("Encryption error: $error");
      return ""; // Or return a suitable error message
    }
  }

static Uint8List generateSecureRandomData(size) {
    return Uint8List.fromList(List<int>.generate(size, (_) => Random.secure().nextInt(256))); // cryptographically secure
  }

在此代码中,我尝试传递我的用户名和密码纯文本并将其返回为加密的 但是当我在 api 调用上使用这些加密文本时,它显示无效凭证,可能是我的加密逻辑不正确 下面我粘贴我的服务器端 C# 代码

public string UserNamePasswordEncryption(stringplaintext)
{
  var key = _configuration.GetSection("keypath").GetValue<string>("key");
  using var aes = new AesCcm(Encoding.UTF8.GetBytes(key));
  var nonce = new byte[AesGcm.NonceByteSizes.MaxSize];
  RandomNumberGenerator.Fill(nonce);
  var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
  var ciphertextBytes = new byte[plaintextBytes.Length];
  var tag = new byte[AesGcm.TagByteSizes.MaxSize];
  aes.Encrypt(nonce, plaintextBytes, ciphertextBytes, tag);
  return new AesGcmCiphertext(nonce, tag, ciphertextBytes).ToString();
}

剩余的C#代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace model.Models
{
    public class AesGcmCiphertext
    {
        public byte[] Nonce { get; }
        public byte[] Tag { get; }
        public byte[] CiphertextBytes { get; }
        public static AesGcmCiphertext FromBase64String(string data)
        {
            var dataBytes = Convert.FromBase64String(data);
            return new AesGcmCiphertext(
                dataBytes.Take(AesGcm.NonceByteSizes.MaxSize).ToArray(),
                dataBytes[^AesGcm.TagByteSizes.MaxSize..],
                dataBytes[AesGcm.NonceByteSizes.MaxSize..^AesGcm.TagByteSizes.MaxSize]
            );
        }
        public AesGcmCiphertext(byte[] nonce, byte[] tag, byte[] ciphertextBytes)
        {
            Nonce = nonce;
            Tag = tag;
            CiphertextBytes = ciphertextBytes;
        }
        public override string ToString()
        {

            return Convert.ToBase64String(Nonce.Concat(CiphertextBytes).Concat(Tag).ToArray());
        }
    }
}

我不知道我的 dart 是这个 C# 的替代代码

c# flutter dart aes pointycastle
1个回答
0
投票

这两个代码有以下几点不同:

  • C#代码使用CCM作为操作模式,Dart代码使用GCM。
  • 在 C# 代码中,返回随机数、密文和标签的串联作为结果,在 Dart 代码中仅返回密文和标签的串联(随机数丢失)。

为了使两种实现兼容,并且由于 C# 代码是参考代码,因此必须相应地调整 Dart 代码,例如:

import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/block/aes.dart';
import 'package:pointycastle/block/modes/ccm.dart';
import 'package:pointycastle/random/fortuna_random.dart';

...

final keyBytes = Uint8List.fromList(utf8.encode(key));
final nonce = getSecureRandom().nextBytes(12);
final plaintextBytes = utf8.encode(plaintext);
final cipher = CCMBlockCipher(AESEngine()) // apply CCM
    ..init(true, AEADParameters(KeyParameter(keyBytes), 128, nonce, Uint8List(0)));
final ciphertextBytes = cipher.process(Uint8List.fromList(plaintextBytes)); // returns ciphertext | tag
return base64.encode(nonce + ciphertextBytes); // create nonce | ciphertext | tag

...

SecureRandom getSecureRandom() {
    List<int> seed = List<int>.generate(32, (_) => Random.secure().nextInt(256));
    return FortunaRandom()..seed(KeyParameter(Uint8List.fromList(seed)));
}

备注:

  • 关于C#代码:在C#代码中,使用CCM进行加密,但有时会应用GCM类。这是一种糟糕的风格,因为它具有误导性(正如你所经历的那样)。相反,应使用相应的 CCM 类(或在用户定义类的情况下重命名),并且如果常量不同(例如

    AesGcm.NonceByteSizes.MaxSize
    = 12 和
    AesCcm.NonceByteSizes.MaxSize
    = 13),则应明确定义这些。

  • 关于使用的 CSPRNG:通过

    SecureRandom
    类,PointyCastle 提供了一个 CSPRNG,其中有各种算法的实现,例如Fortuna 算法,在
    FortunaRandom
    类中实现。由于由于加密,您已经在使用 PointyCastle(因此这并不意味着额外的依赖),因此也可以使用 PointyCastle 的 CSPRNG。这有更好的记录,因此比指定不明确的
    Random.secure()
    (通常仅用作使用
    SecureRandom
    /
    FortunaRandom
    时生成种子的熵源)更透明。

  • 关于密钥:一般来说,UTF-8编码意味着密码或密码短语,它通常比随机字节序列具有更小的熵。在这种情况下,不应将密码直接用作密钥,而应应用密钥派生函数(例如 Argon2 或至少 PBKDF2)来从密码中派生出actual密钥,并结合每个密钥的随机盐加密。盐不是秘密的,它与密文(通常是串联的)一起提供给解密方。

  • 关于算法的选择:您可能有理由使用 CCM 而不是 GCM(遗留应用程序等),但通常(更现代的)GCM 被认为优于 CCM,因此应该应用,请参见例如这里

© www.soinside.com 2019 - 2024. All rights reserved.