secretkeyspec cipher c# java encryption aes rijndaelmanaged

cipher - Encriptar AES con C#para que coincida con el cifrado de Java



rijndael java (2)

Me han proporcionado una implementación de Java para el cifrado, pero desafortunadamente somos una tienda de .net y no tenemos forma de incorporar Java en nuestra solución. Tristemente, tampoco soy un chico de Java, así que he estado luchando con esto durante unos días y pensé que finalmente volvería aquí para pedir ayuda.

He buscado alto y bajo para que coincida con la forma en que funciona el cifrado de Java y he llegado a la resolución de que necesito usar Rijndael administrado en c #. En realidad estoy muy cerca. Las cadenas que estoy devolviendo en c # coinciden con la primera mitad, pero la segunda mitad es diferente.

Aquí hay un fragmento de la implementación de java:

private static String EncryptBy16( String str, String theKey) throws Exception { if ( str == null || str.length() > 16) { throw new NullPointerException(); } int len = str.length(); byte[] pidBytes = str.getBytes(); byte[] pidPaddedBytes = new byte[16]; for ( int x=0; x<16; x++ ) { if ( x<len ) { pidPaddedBytes[x] = pidBytes[x]; } else { pidPaddedBytes[x] = (byte) 0x0; } } byte[] raw = asBinary( theKey ); SecretKeySpec myKeySpec = new SecretKeySpec( raw, "AES" ); Cipher myCipher = Cipher.getInstance( "AES/ECB/NoPadding" ); cipher.init( Cipher.ENCRYPT_MODE, myKeySpec ); byte[] encrypted = myCipher.doFinal( pidPaddedBytes ); return( ByteToString( encrypted ) ); } public static String Encrypt(String stringToEncrypt, String key) throws Exception { if ( stringToEncrypt == null ){ throw new NullPointerException(); } String str = stringToEncrypt; StringBuffer result = new StringBuffer(); do{ String s = str; if(s.length() > 16){ str = s.substring(16); s = s.substring(0,16); }else { str = null; } result.append(EncryptBy16(s,key)); }while(str != null); return result.toString(); }

No estoy del todo seguro de por qué solo están pasando 16 caracteres a la vez, pero w / e. Intenté lo mismo con mi implementación de c # usando un generador de cadenas y solo enviando 16 caracteres a la vez y obtuve el mismo resultado que obtuve cuando paso toda la cadena a la vez.

Aquí hay un fragmento de mi implementación de c # que es principalmente copiar y pegar desde el sitio de MS para RijndaelManaged:

public static string Encrypt(string stringToEncrypt, string key) { using (RijndaelManaged myRijndael = new RijndaelManaged()) { myRijndael.Key = StringToByte(key); myRijndael.IV = new byte[16]; return EncryptStringToBytes(stringToEncrypt, myRijndael.Key, myRijndael.IV); } } static string EncryptStringToBytes(string plainText, byte[] Key, byte[] IV) { if (plainText == null || plainText.Length <= 0) throw new ArgumentNullException("plainText"); if (Key == null || Key.Length <= 0) throw new ArgumentNullException("Key"); if (IV == null || IV.Length <= 0) throw new ArgumentNullException("Key"); byte[] encrypted; using (RijndaelManaged rijAlg = new RijndaelManaged()) { rijAlg.Key = Key; rijAlg.IV = IV; ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV); using (MemoryStream msEncrypt = new MemoryStream()) { using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) { swEncrypt.Write(plainText); } encrypted = msEncrypt.ToArray(); } } } return ByteToString(encrypted); }

Como dije antes, la primera mitad de la cadena cifrada es la misma (ver un ejemplo a continuación), pero la segunda mitad está desactivada. He agregado espacios en los resultados a continuación para ilustrar mejor dónde está la diferencia. No sé lo suficiente sobre encriptación ni Java para saber a dónde recurrir. Cualquier orientación sería muy apreciada

Salida de Java:

49a85367ec8bc387bb44963b54528c97 8026d7eaeff9e4cb7cf74f8227f80752

Salida C #:

49a85367ec8bc387bb44963b54528c97 718f574341593be65034627a6505f13c

Actualización según la sugerencia de Chris a continuación:

static string EncryptStringToBytes(string plainText, byte[] Key, byte[] IV) { if (plainText == null || plainText.Length <= 0) throw new ArgumentNullException("plainText"); if (Key == null || Key.Length <= 0) throw new ArgumentNullException("Key"); if (IV == null || IV.Length <= 0) throw new ArgumentNullException("Key"); byte[] encrypted; using (RijndaelManaged rijAlg = new RijndaelManaged()) { rijAlg.Key = Key; rijAlg.IV = IV; rijAlg.Padding = PaddingMode.None; rijAlg.Mode = CipherMode.ECB; ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV); using (MemoryStream msEncrypt = new MemoryStream()) { using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) { using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) { swEncrypt.Write(plainText); if (plainText.Length < 16) { for (int i = plainText.Length; i < 16; i++) { swEncrypt.Write((byte)0x0); } } } encrypted = msEncrypt.ToArray(); } } } return ByteToString(encrypted); }


Gran pregunta, este es un error común al trabajar con el mismo algoritmo de cifrado pero en diferentes idiomas. La implementación de los detalles del algoritmo requiere atención. No he probado el código, pero en su caso las opciones de relleno de dos implementaciones son diferentes, intente utilizar las mismas opciones de relleno para las implementaciones de c # y Java. Puede leer los comentarios y más sobre la implementación desde aquí . Por favor, preste atención a los valores predeterminados de relleno.

  • Padding = PaddingMode.PKCS7,
  • final privado String cipherTransformation = "AES / CBC / PKCS5Padding";

Implementación c #:

public RijndaelManaged GetRijndaelManaged(String secretKey) { var keyBytes = new byte[16]; var secretKeyBytes = Encoding.UTF8.GetBytes(secretKey); Array.Copy(secretKeyBytes, keyBytes, Math.Min(keyBytes.Length, secretKeyBytes.Length)); return new RijndaelManaged { Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7, KeySize = 128, BlockSize = 128, Key = keyBytes, IV = keyBytes }; } public byte[] Encrypt(byte[] plainBytes, RijndaelManaged rijndaelManaged) { return rijndaelManaged.CreateEncryptor() .TransformFinalBlock(plainBytes, 0, plainBytes.Length); } public byte[] Decrypt(byte[] encryptedData, RijndaelManaged rijndaelManaged) { return rijndaelManaged.CreateDecryptor() .TransformFinalBlock(encryptedData, 0, encryptedData.Length); } // Encrypts plaintext using AES 128bit key and a Chain Block Cipher and returns a base64 encoded string public String Encrypt(String plainText, String key) { var plainBytes = Encoding.UTF8.GetBytes(plainText); return Convert.ToBase64String(Encrypt(plainBytes, GetRijndaelManaged(key))); } public String Decrypt(String encryptedText, String key) { var encryptedBytes = Convert.FromBase64String(encryptedText); return Encoding.UTF8.GetString(Decrypt(encryptedBytes, GetRijndaelManaged(key))); }

Implementación de Java:

private final String characterEncoding = "UTF-8"; private final String cipherTransformation = "AES/CBC/PKCS5Padding"; private final String aesEncryptionAlgorithm = "AES"; public byte[] decrypt(byte[] cipherText, byte[] key, byte [] initialVector) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { Cipher cipher = Cipher.getInstance(cipherTransformation); SecretKeySpec secretKeySpecy = new SecretKeySpec(key, aesEncryptionAlgorithm); IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector); cipher.init(Cipher.DECRYPT_MODE, secretKeySpecy, ivParameterSpec); cipherText = cipher.doFinal(cipherText); return cipherText; } public byte[] encrypt(byte[] plainText, byte[] key, byte [] initialVector) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { Cipher cipher = Cipher.getInstance(cipherTransformation); SecretKeySpec secretKeySpec = new SecretKeySpec(key, aesEncryptionAlgorithm); IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); plainText = cipher.doFinal(plainText); return plainText; } private byte[] getKeyBytes(String key) throws UnsupportedEncodingException{ byte[] keyBytes= new byte[16]; byte[] parameterKeyBytes= key.getBytes(characterEncoding); System.arraycopy(parameterKeyBytes, 0, keyBytes, 0, Math.min(parameterKeyBytes.length, keyBytes.length)); return keyBytes; } public String encrypt(String plainText, String key) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException{ byte[] plainTextbytes = plainText.getBytes(characterEncoding); byte[] keyBytes = getKeyBytes(key); return Base64.encodeToString(encrypt(plainTextbytes,keyBytes, keyBytes), Base64.DEFAULT); } public String decrypt(String encryptedText, String key) throws KeyException, GeneralSecurityException, GeneralSecurityException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException{ byte[] cipheredBytes = Base64.decode(encryptedText, Base64.DEFAULT); byte[] keyBytes = getKeyBytes(key); return new String(decrypt(cipheredBytes, keyBytes, keyBytes), characterEncoding); }


Su traducción de C # parece que está haciendo lo correcto en su mayor parte, porque el primer bloque coincide. Lo que no concuerda es el último bloque, y es porque el código de Java es cero-relleno del último bloque para completarlo, mientras que su código C # no hace eso, por lo que usaría el relleno PKCS # 5 de manera predeterminada.

El relleno de PKCS # 5 es mucho mejor que el relleno cero, por supuesto, pero como este último es el que usaba el código de Java, tendrías que hacer lo mismo. (Eso significa, llame a swEncrypt.Write((byte) 0) algunas veces más hasta que el conteo de bytes sea un múltiplo de 16.

Hay otra sutileza. El código de Java traduce la cadena a bytes utilizando String.getBytes() , que utiliza la "codificación predeterminada" del tiempo de ejecución de Java. Esto significa que si su cadena contiene caracteres no ASCII, se encontraría con problemas de interoperabilidad. La mejor práctica es usar UTF-8, pero dado que no puede cambiar el código de Java, supongo que no hay mucho que pueda hacer al respecto.