簡體   English   中英

將Java加密例程移植到C#

[英]Porting Java encryption routine to C#

我試圖通過移植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=

您的代碼幾乎按預期工作。 只是你以某種方式混淆了Java版本的輸出(可能還有C#版本)。

如果我執行你的Java代碼(使用Guava 18.0的JDK 7和8),我會得到

Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U

如果我執行你的C#代碼( DEMO ),我明白了

Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U1

因此,C#版本最后還有一個“1”。 它應該是填充字符,但不是。 這意味着HttpServerUtility.UrlTokenEncode()不提供符合標准的URL安全Base64編碼,您不應該使用它。 見此問答

URL安全的Base64編碼可以很容易地從普通的Base64編碼中導出(比較RFC4648中的表1和2),如Marc Gravell的回答所示

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

有:

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

那不是全部。 如果我們把你的Java輸出

Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=

解密它,然后我們得到以下標記:

{"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

這是一個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

正如你在這里看到的:

程序輸出的屏幕截圖。 Top是C#版本,底部是Java版本(IntelliJ Idea 14.1.5)

Java版本是從您的代碼中復制/粘貼的,並且正在使用guava-18.0並使用JDK8 x64編譯(我不是Java專家所以我只是添加它們以防它們有任何區別)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM