【问题标题】:Porting Java encryption routine to C#将 Java 加密例程移植到 C#
【发布时间】:2023-03-27 22:28:01
【问题描述】:

我尝试移植 Google 的代码以为其验证码 (https://github.com/google/recaptcha-java/blob/master/appengine/src/main/java/com/google/recaptcha/STokenUtils.java) 生成安全令牌,但收效甚微:

原始实用程序具有以下内容:

private static final String CIPHER_INSTANCE_NAME = "AES/ECB/PKCS5Padding";

private static String encryptAes(String input, String siteSecret) {
    try {
      SecretKeySpec secretKey = getKey(siteSecret);
      Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE_NAME);
      cipher.init(Cipher.ENCRYPT_MODE, secretKey);
      return BaseEncoding.base64Url().omitPadding().encode(cipher.doFinal(input.getBytes("UTF-8")));
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

private static SecretKeySpec getKey(String siteSecret){
    try {
      byte[] key = siteSecret.getBytes("UTF-8");
      key = Arrays.copyOf(MessageDigest.getInstance("SHA").digest(key), 16);
      return new SecretKeySpec(key, "AES");
    } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
      e.printStackTrace();
    }
    return null;
  }

public static void main(String [] args) throws Exception {
    //Hard coded the following to get a repeatable result
    String siteSecret = "12345678";
    String jsonToken = "{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}";
    System.out.println(" json token: " + jsonToken);
    System.out.println(" siteSecret: " + siteSecret);
    System.out.println(" Encrypted stoken: " + encryptAes(jsonToken, siteSecret));

鉴于我硬编码的值,我将Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns" 作为我的加密令牌返回。

我的 Java 和加密技能有点生疏,而且 C# 中并不总是有直接的类似物。我试图将encrypeAes()getKey() 与以下内容合并,这是不正确的:

public static string EncryptText(string PlainText, string siteSecret)
{
    using (RijndaelManaged aes = new RijndaelManaged())
    {
        aes.Mode = CipherMode.ECB;
        aes.Padding = PaddingMode.PKCS7;
        var bytes = Encoding.UTF8.GetBytes(siteSecret);
        SHA1 sha1 = SHA1.Create();
        var shaKey = sha1.ComputeHash(bytes);

        byte[] targetArray = new byte[16];
        Array.Copy(shaKey, targetArray, 16);

        aes.Key = targetArray;

        ICryptoTransform encrypto = aes.CreateEncryptor();

        byte[] plainTextByte = ASCIIEncoding.UTF8.GetBytes(PlainText);
        byte[] CipherText = encrypto.TransformFinalBlock(plainTextByte, 0, plainTextByte.Length);
        return HttpServerUtility.UrlTokenEncode(CipherText); //Equivalent to java's BaseEncoding.base64Url()?
    }
}

C# 版本生成的值不正确:Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=

【问题讨论】:

  • 实现了 SHA1 哈希,仍然没有乐趣。
  • 您可以尝试使用AesManaged 而不是RijndaelManaged(默认情况下具有不同的密钥大小,因此不是标准 AES)以匹配Java部分CIPHER_INSTANCE_NAME = "AES/ECB/PKCS5Padding" 并使用 Convert.ToBase64String 而不是 HttpServerUtility.UrlTokenEncode...
  • 更新为包含 c# 输出。 @pasty 我更新为AESManaged,但不清楚我将如何与密码名称保持一致。我选择了PaddingMode.PKCS7blog.zebsadiq.com/post/…,但我承认我一直在摸索。更改为 Convert.ToBase64String 对结果没有影响。我选择了HttpServerUtility.UrlTokenEncode,因为它似乎最接近 Java 的 BaseEncoding.base64Url()。
  • @pasty RijndaelManaged的块大小是128 bit by default,所以在功能上和AesManaged是等价的。
  • @ArtjomB。谢谢你的提示,我以为是 256。

标签: java c# encryption


【解决方案1】:

您的代码几乎可以按预期运行。只是您以某种方式混淆了 Java 版本(可能还有 C# 版本)的输出。

如果我执行你的 Java 代码(JDK 7 & 8 with Guava 18.0),我会得到

Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U

如果我执行你的 C# 代码 (DEMO),我会得到

Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U1

因此,C# 版本的末尾多了一个“1”。它应该是一个填充字符,但不是。这意味着HttpServerUtility.UrlTokenEncode() 没有提供符合标准的 URL 安全 Base64 编码,因此您不应使用它。另见this Q&A

如 Marc Gravell 的 this answer 所示,可以轻松地从正常的 Base64 编码(比较 RFC4648 中的表 1 和表 2)派生 URL 安全的 Base64 编码:

string returnValue = System.Convert.ToBase64String(toEncodeAsBytes)
        .TrimEnd(padding).Replace('+', '-').Replace('/', '_');

与:

static readonly char[] padding = { '=' };

这还不是全部。如果我们采用您的 Java 输出

Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=

decrypt它,然后我们得到以下令牌:

{"session_id":"4182e173-3a24-4c10-b76c-b85a36be1173","ts_ms":1445786965574}

这与您在代码中拥有的令牌不同:

{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}

剩下的主要问题是您使用了无效的 JSON。 JSON 中的字符串和键需要包装在 " 中,而不是 '

这意味着加密令牌实际上应该是(使用代码中令牌的有效版本):

D9rOP07fYgBfza5vbGsvdPe8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBsAWBDgtdSozv4jS_auBU-CgjlrJ_430LgYcathLLd9U

【讨论】:

    【解决方案2】:

    这是一个 C# 实现,它重现了与您的 Java 代码相同的结果:

    class Program
    {
        public static byte[] GetKey(string siteSecret)
        {
            byte[] key = Encoding.UTF8.GetBytes(siteSecret);
            return SHA1.Create().ComputeHash(key).Take(16).ToArray();
        }
    
        public static string EncryptAes(string input, string siteSecret)
        {
            var key = GetKey(siteSecret);
            using (var aes = AesManaged.Create())
            {
                if (aes == null) return null;
    
                aes.Mode = CipherMode.ECB;
                aes.Padding = PaddingMode.PKCS7;
                aes.Key = key;
                byte[] inputBytes = Encoding.UTF8.GetBytes(input);
    
                var enc = aes.CreateEncryptor(key, new byte[16]);
                return UrlSafeBase64(enc.TransformFinalBlock(inputBytes,0,input.Length));
            }
        }
    
        // http://stackoverflow.com/a/26354677/162671
        public static string UrlSafeBase64(byte[] bytes)
        {
            return Convert.ToBase64String(bytes).TrimEnd('=')
                .Replace('+', '-')
                .Replace('/', '_');
        }
        static void Main(string[] args)
        {
            string siteSecret = "12345678";
            string jsonToken = "{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}";
    
            Console.WriteLine(" json token: " + jsonToken);
            Console.WriteLine(" siteSecret: " + siteSecret);
            Console.WriteLine(EncryptAes(jsonToken, siteSecret));
            Console.ReadLine();
        }
    }
    

    我不知道你为什么说你从 Java 程序中得到 Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns,因为我没有得到那个输出。我从 C# 版本和 Java 版本得到的输出是这样的:

    Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U

    如您所见:

    Java 版本是从您的代码中复制/粘贴的,使用的是 guava-18.0 并使用 JDK8 x64 编译(我不是 Java 专家,所以我只是添加这些以防有什么不同)。

    【讨论】:

      猜你喜欢
      • 2012-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多