2015-10-25 5 views
5

Sto tentando con scarso successo alla porta sopra il codice di Google per generare un token sicuro per la loro captcha (https://github.com/google/recaptcha-java/blob/master/appengine/src/main/java/com/google/recaptcha/STokenUtils.java):porting Java crittografia di routine per C#

L'utilità originale ha il seguente:

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)); 

Dato i valori che ho codificato, ottengo Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns" come mio token crittografato.

Le mie competenze di Java e crittografia sono più che un po 'arrugginite e non ci sono sempre analoghi diretti in C#. Ho cercato di fondere encrypeAes() e getKey() con la seguente, che non è corretto:

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()? 
    } 
} 

La versione C# produce il valore errato di: Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=

+0

SHA1 Implementato hashing, ancora nessuna gioia. –

+0

Si potrebbe provare a usare ['AesManaged'] (https://msdn.microsoft.com/en-us/library/system.security.cryptography.aesmanaged%28v=vs.100%29.aspx) invece di' RijndaelManaged' (che ha diverse dimensioni della chiave di default e quindi non è lo * standard * AES) per abbinare la parte Java 'CIPHER_INSTANCE_NAME =" AES/ECB/PKCS5Padding "' e usare ['Convert.ToBase64String'] (https: // msdn.microsoft.com/en-us/library/dhx0d524%28v=vs.110%29.aspx) invece di 'HttpServerUtility.UrlTokenEncode ...'. – pasty

+0

Aggiornato per includere l'output C#. @pasty ho aggiornato a 'AESManaged', ma non sono chiaro su come vorrei allineare con il nome del codice. Ho optato per 'PaddingMode.PKCS7' per http://blog.zebsadiq.com/post/AESCBCPKCS5Padding-EncryptionDecryption-in-C.aspx, ma ammetto che sto armeggiando. Il passaggio a 'Convert.ToBase64String' non ha avuto alcun effetto sul risultato. Ho optato per 'HttpServerUtility.UrlTokenEncode' come sembrava più vicino a BaseEncoding.base64Url() di Java. –

risposta

4

Il codice funziona quasi come previsto. È solo che hai in qualche modo confuso gli output della versione di Java (e probabilmente anche della versione di C#).

Se eseguo il codice Java (JDK 7 & 8 con Guava 18,0), ottengo

 
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U 

e se eseguo il codice C# (DEMO), ottengo

 
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U1 

Così , la versione C# ha un ulteriore "1" alla fine. Dovrebbe essere un carattere di riempimento, ma non lo è. Ciò significa che HttpServerUtility.UrlTokenEncode() non fornisce una codifica Base64 conforme agli standard conforme agli standard e non è necessario utilizzarla. Vedi anche this Q&A.

La codifica Base64 URL-safe facilmente deducibile dalla codifica Base64 normale (confrontare tabelle 1 e 2 in RFC4648) visto in this answer Marc Gravell:

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

con:

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

Ma non è tutto. Se prendiamo l'output Java di

 
Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U= 

e decrypt, allora otteniamo la seguente token:

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

che è diverso dal token che si ha nel codice:

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

Il problema principale rimanente è l'utilizzo di JSON non valido. Le stringhe e le chiavi in ​​JSON devono essere racchiuse tra " e non '.

Il che significa che il token crittografato in realtà avrebbe dovuto essere (con una versione valida del token dal codice):

 
D9rOP07fYgBfza5vbGsvdPe8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBsAWBDgtdSozv4jS_auBU-CgjlrJ_430LgYcathLLd9U 
3

Ecco un'implementazione C# che riproduce lo stesso risultato di tua Java codice:

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(); 
    } 
} 

non so il motivo per cui hai detto che stai ricevendo Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns dal programma Java perché non sto ottenendo che in uscita. L'uscita sto ottenendo sia la versione C# e la versione Java è questo:

Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U

Come si può vedere qui:

Screenshot of the programs output. Top is the C# version and bottom is the Java version (IntelliJ Idea 14.1.5)

La versione Java è stato copia/incollato dal codice e sta usando guava-18,0 e compilato con JDK8 x64 (io non sono un esperto di Java, quindi sono solo l'aggiunta di questi nel caso in cui non fa alcuna differenza) .