2017-08-26 210 views
0

有一個服務器在C#(方法encryptCSharp)上加密密碼,android應用程序接收加密的密碼,salt,passPhrase和initVector。 我必須在Java中解密該密碼。 服務器人向我發送了C#中的加密方法,我需要創建encryptJava和decryptJava方法,這些方法在Java中與C#中的工作方式相同。 要創建在Java中缺席的PasswordDeriveBytes我使用這裏的示例Encryption Diff Between Java and C#C#和Java之間的加密/解密

所以,我的問題是,我的Java方法出了什麼問題?兩者都沒有工作

更新:我做了片段的一些變化,現在所有的作品!

我調用一個方法:

String encryptedText = encryptJava("12345", "100", "@.erf.net34", "@[email protected]");//it works!! 
String decryptedText = decryptJava(encryptedText, "100", "@.erf.net34", "@[email protected]");//it doesn't work!! 

這裏是我使用的Java方法和從服務器C#方法。

C#(我不能改變)

public static String encryptCSharp(String plainText, String saltValue, String passPhrase, String initVector) { 
    String hashAlgorithm = "SHA1"; 
    int passwordIterations = 1; 
    int keySize = 256; 
    // Convert strings into byte arrays. 
    // Let us assume that strings only contain ASCII codes. 
    // If strings include Unicode characters, use Unicode, UTF7, or UTF8 
    // encoding. 
    byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector); 
    byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue); 

    // Convert our plaintext into a byte array. 
    // Let us assume that plaintext contains UTF8-encoded characters. 
    byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); 

    // First, we must create a password, from which the key will be derived. 
    // This password will be generated from the specified passphrase and 
    // salt value. The password will be created using the specified hash 
    // algorithm. Password creation can be done in several iterations. 
    PasswordDeriveBytes password = new PasswordDeriveBytes(
      passPhrase, 
      saltValueBytes, 
      hashAlgorithm, 
      passwordIterations); 

    // Use the password to generate pseudo-random bytes for the encryption 
    // key. Specify the size of the key in bytes (instead of bits). 
    byte[] keyBytes = password.GetBytes(keySize/8); 

    // Create uninitialized Rijndael encryption object. 
    RijndaelManaged symmetricKey = new RijndaelManaged(); 

    // It is reasonable to set encryption mode to Cipher Block Chaining 
    // (CBC). Use default options for other symmetric key parameters. 
    symmetricKey.Mode = CipherMode.CBC; 

    // Generate encryptor from the existing key bytes and initialization 
    // vector. Key size will be defined based on the number of the key 
    // bytes. 
    ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
      keyBytes, 
      initVectorBytes); 

    // Define memory stream which will be used to hold encrypted data. 
    MemoryStream memoryStream = new MemoryStream(); 

    // Define cryptographic stream (always use Write mode for encryption). 
    CryptoStream cryptoStream = new CryptoStream(memoryStream, 
      encryptor, 
      CryptoStreamMode.Write); 
    // Start encrypting. 
    cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); 

    // Finish encrypting. 
    cryptoStream.FlushFinalBlock(); 

    // Convert our encrypted data from a memory stream into a byte array. 
    byte[] cipherTextBytes = memoryStream.ToArray(); 

    // Close both streams. 
    memoryStream.Close(); 
    cryptoStream.Close(); 

    // Convert encrypted data into a base64-encoded string. 

    String cipherText = Convert.ToBase64String(cipherTextBytes); 

    // Return encrypted string. 
    return cipherText; 
} 

的Java 加密的消息,它的工作原理,我得到了相同的結果的方法在C#

private String encryptJava(String plainText, String saltValue, String passPhrase, String initVector) {//working!!! 
    String result = ""; 

    byte[] initVectorBytes = initVector.getBytes(US_ASCII); 
    byte[] saltValueBytes = saltValue.getBytes(US_ASCII); 
    byte[] plainTextBytes = plainText.getBytes(UTF_8); 

    Cipher cipher; 
    try { 
     final com.gmail.example.PasswordDeriveBytes password = new com.gmail.example.PasswordDeriveBytes(passPhrase, saltValueBytes); 
     final byte[] keyBytes = password.getBytes(256/Byte.SIZE); 
     SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); 

     cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
     try { 
      cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(initVectorBytes)); 
     } catch (InvalidAlgorithmParameterException e) { 
      e.printStackTrace(); 
     } 

     final byte[] ct = cipher.doFinal(plainTextBytes); 
     result = Base64.encodeToString(ct, Base64.DEFAULT);//**added this line!** 
     //result = new String(ct, "US-ASCII");**-- deleted this line!** 
    } catch (NoSuchAlgorithmException e) { 
     e.printStackTrace(); 
    } catch (NoSuchPaddingException e) { 
     e.printStackTrace(); 
    } catch (InvalidKeyException e) { 
     e.printStackTrace(); 
    } catch (BadPaddingException e) { 
     e.printStackTrace(); 
    } catch (IllegalBlockSizeException e) { 
     e.printStackTrace(); 
    } 
    return result; 
} 

Java方法模擬C#中的相同方法#

public class PasswordDeriveBytes { 

    private final MessageDigest hash; 

    private final byte[] firstToLastDigest; 
    private final byte[] outputBuffer; 

    private int position = 0; 

    public PasswordDeriveBytes(String password, byte[] salt) { 
     try { 
      this.hash = MessageDigest.getInstance("SHA-1"); 

      this.hash.update(password.getBytes(UTF_8)); 
      this.hash.update(salt); 
      this.firstToLastDigest = this.hash.digest(); 

      final int iterations = 1;//**changed from 100** 
      for (int i = 1; i < iterations - 1; i++) { 
       hash.update(firstToLastDigest); 
       hash.digest(firstToLastDigest, 0, firstToLastDigest.length); 
      } 

      this.outputBuffer = hash.digest(firstToLastDigest); 

     } catch (NoSuchAlgorithmException | DigestException e) { 
      throw new IllegalStateException("SHA-1 digest should always be available", e); 
     } 
    } 

    public byte[] getBytes(int requested) { 
     if (requested < 1) { 
      throw new IllegalArgumentException(
        "You should at least request 1 byte"); 
     } 

     byte[] result = new byte[requested]; 

     int generated = 0; 

     try { 
      while (generated < requested) { 
       final int outputOffset = position % outputBuffer.length; 
       if (outputOffset == 0 && position != 0) { 
        final String counter = String.valueOf(position/outputBuffer.length); 
        hash.update(counter.getBytes(US_ASCII)); 
        hash.update(firstToLastDigest); 
        hash.digest(outputBuffer, 0, outputBuffer.length); 
       } 

       final int left = outputBuffer.length - outputOffset; 
       final int required = requested - generated; 
       final int copy = Math.min(left, required); 

       System.arraycopy(outputBuffer, outputOffset, result, generated, copy); 

       generated += copy; 
       position += copy; 
      } 
     } catch (final DigestException e) { 
      throw new IllegalStateException(e); 
     } 
     return result; 
    } 
} 

最後Java方法不起作用並嘗試使用erstand我做什麼錯

private String decryptJava(String encryptedText, String saltValue, String passPhrase, String initVector) { 
    String result = ""; 
    byte[] initVectorBytes = initVector.getBytes(US_ASCII); 
    byte[] saltValueBytes = saltValue.getBytes(US_ASCII); 
    byte[] encryptedTexttBytes = Base64.decode(encryptedText, Base64.DEFAULT); 
    Cipher cipher; 
    try { 
     final PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltValueBytes); 
     final byte[] keyBytes = password.getBytes(256/Byte.SIZE); 
     SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); 

     cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
     try { 
      cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(initVectorBytes)); 
     } catch (InvalidAlgorithmParameterException e) { 
      e.printStackTrace(); 
     } 
     final byte[] ct = cipher.doFinal(encryptedTexttBytes); 
     //result = Base64.encodeToString(ct, Base64.DEFAULT); - **deleted this line** 
     try { 
      result = new String(ct, "US-ASCII");//** added this line** 
     } catch (UnsupportedEncodingException e) { 
      e.printStackTrace(); 
     } 

    } catch (NoSuchAlgorithmException e) { 
     e.printStackTrace(); 
    } catch (NoSuchPaddingException e) { 
     e.printStackTrace(); 
    } catch (InvalidKeyException e) { 
     e.printStackTrace(); 
    } catch (BadPaddingException e) { 
     e.printStackTrace(); 
    } catch (IllegalBlockSizeException e) { 
     e.printStackTrace(); 
    } 
    return result; 
} 

回答

1

隨着PasswordDeriveBytes永遠永遠比底層散列函數多個字節。該函數實現PBKDF1,不能輸出超過該位數(在本例中爲SHA-1爲160)。

微軟的實現允許更多的輸出,但是實現被打破到極致(甚至可能重複輸出!)。使用Rfc2898DeriveBytes代替實現PBKDF2,也可以在Java中使用。使用更大的散列,PBKDF2可以產生比散列輸出更多的字節,但僅以安全爲代價。

+0

我沒有經驗的密碼學,所以我試着理解你的答案。你的意思是,與微軟服務器合作的人必須將方法從'PasswordDeriveBytes'改爲'Rfc2898DeriveBytes'? – LumisD

+0

是的,那肯定是可取的。 'PasswordDeriveBytes'已被'Rfc2898DeriveBytes'取代。據我所知,沒有人完全描述了早期使用的擴展算法(它可能包含一些可怕的緩衝區溢出)。 –

0

最後,我發現用於編碼和deconding方法的溶液: (以便不增加此處的代碼量,我提出在上面的片斷的變化)在encryptJava方法

我改變一條線,

in PasswordDeriveBytes我將迭代次數從100改爲1

in decryptJava方法我添加了一行並刪除了一行。

+0

很高興你的工作,但請注意,通過使用'PasswordDeriveBytes'和1作爲迭代計數,你實際上沒有使它*安全*,我希望你是什麼後。 –

+0

@MaartenBodewes,服務器加密方法不在我的範圍內,但我會向服務器人員展示您的筆記以解決該問題。儘管我發現自己是這個問題的答案,但如果不使用Java的方法 - 「PasswordDeriveBytes」,解決這個問題是無法解決的。非常感謝你的回答! – LumisD