问题
I'm facing an issue trying to Decrypt a string which has been encrypted in Java with the following properties (Java code)
public static Builder getDefaultBuilder(String key, String salt, byte[] iv) {
return new Builder()
.setIv(iv)
.setKey(key)
.setSalt(salt)
.setKeyLength(128)
.setKeyAlgorithm("AES")
.setCharsetName("UTF8")
.setIterationCount(1)
.setDigestAlgorithm("SHA1")
.setBase64Mode(Base64.DEFAULT)
.setAlgorithm("AES/CBC/PKCS5Padding")
.setSecureRandomAlgorithm("SHA1PRNG")
.setSecretKeyType("PBKDF2WithHmacSHA1");
}
This is my code so far (C#)
public string DecryptText(string encryptedString)
{
using (myRijndael = new RijndaelManaged())
{
myRijndael.Key = Convert.FromBase64String(encryptionKey);
myRijndael.IV = new byte[16];
myRijndael.Mode = CipherMode.CBC;
myRijndael.Padding = PaddingMode.PKCS7;
Byte[] ourEnc = Convert.FromBase64String(encryptedString);
string ourDec = DecryptStringFromBytes(ourEnc, myRijndael.Key, myRijndael.IV);
return ourDec;
}
}
protected string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
But when I try to decrypt I get the following exception "System.Security.Cryptography.CryptographicException: 'Specified key is not a valid size for this algorithm.' ".
The origin of the Java code resides here https://github.com/simbiose/Encryption/blob/master/Encryption/main/se/simbio/encryption/Encryption.java
This is the Java code when encrypting
public String encrypt(String data) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException {
if (data == null) return null;
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
byte[] dataBytes = data.getBytes(mBuilder.getCharsetName());
Cipher cipher = Cipher.getInstance(mBuilder.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, secretKey, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
return Base64.encodeToString(cipher.doFinal(dataBytes), mBuilder.getBase64Mode());
}
private SecretKey getSecretKey(char[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance(mBuilder.getSecretKeyType());
KeySpec spec = new PBEKeySpec(key, mBuilder.getSalt().getBytes(mBuilder.getCharsetName()), mBuilder.getIterationCount(), mBuilder.getKeyLength());
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), mBuilder.getKeyAlgorithm());
}
private char[] hashTheKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance(mBuilder.getDigestAlgorithm());
messageDigest.update(key.getBytes(mBuilder.getCharsetName()));
return Base64.encodeToString(messageDigest.digest(), Base64.NO_PADDING).toCharArray();
}
I've been struggling with this for two day since I haven't really worked a lot with encryption, so any help would be greatly appreciated.
Thanks!
Update: Whole class
public sealed class MyCryptoClass
{
protected RijndaelManaged myRijndael;
private static string encryptionKey = "random";
// Singleton pattern used here with ensured thread safety
protected static readonly MyCryptoClass _instance = new MyCryptoClass();
public static MyCryptoClass Instance
{
get { return _instance; }
}
public MyCryptoClass()
{
}
public string DecryptText(string encryptedString)
{
using (myRijndael = new RijndaelManaged())
{
myRijndael.Key = Convert.FromBase64String(encryptionKey);
myRijndael.IV = new byte[16];
myRijndael.Mode = CipherMode.CBC;
myRijndael.Padding = PaddingMode.PKCS7;
Byte[] ourEnc = Convert.FromBase64String(encryptedString);
string ourDec = DecryptStringFromBytes(ourEnc, myRijndael.Key, myRijndael.IV);
return ourDec;
}
}
public string EncryptText(string plainText)
{
using (myRijndael = new RijndaelManaged())
{
myRijndael.Key = HexStringToByte(encryptionKey);
myRijndael.IV = HexStringToByte(initialisationVector);
myRijndael.Mode = CipherMode.CBC;
myRijndael.Padding = PaddingMode.PKCS7;
byte[] encrypted = EncryptStringToBytes(plainText, myRijndael.Key, myRijndael.IV);
string encString = Convert.ToBase64String(encrypted);
return encString;
}
}
protected byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
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;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
protected string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
public static void GenerateKeyAndIV()
{
// This code is only here for an example
RijndaelManaged myRijndaelManaged = new RijndaelManaged();
myRijndaelManaged.Mode = CipherMode.CBC;
myRijndaelManaged.Padding = PaddingMode.PKCS7;
myRijndaelManaged.GenerateIV();
myRijndaelManaged.GenerateKey();
string newKey = ByteArrayToHexString(myRijndaelManaged.Key);
string newinitVector = ByteArrayToHexString(myRijndaelManaged.IV);
}
protected static byte[] HexStringToByte(string hexString)
{
try
{
int bytesCount = (hexString.Length) / 2;
byte[] bytes = new byte[bytesCount];
for (int x = 0; x < bytesCount; ++x)
{
bytes[x] = Convert.ToByte(hexString.Substring(x * 2, 2), 16);
}
return bytes;
}
catch
{
throw;
}
}
public static string ByteArrayToHexString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
}
回答1:
Since your
MyCryptoClass.encryptionKey
corresponds to yourEncryption.Builder.mKey
you have to generate the secret key on the C#-side i.e. you have to implement on the C# side a counterpart for each Java-method involved in this process. These Java-methods aregetSecretKey(char[] key)
,hashTheKey(String key)
and alsothird.part.android.util.Base64.encodeToString(byte[] input, int flags)
.Possible C#-Counterpart of the Java-method
getSecretKey(char[] key)
:private static byte[] GetSecretKey() { string hashedKey = GetHashedKey(); byte[] saltBytes = Encoding.UTF8.GetBytes(salt); // builder.mCharsetName = "UTF8"; int iterations = 1; // builder.mIterationCount = 1 byte[] secretKey = null; using (Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(hashedKey, saltBytes, iterations)) // builder.mSecretKeyType = "PBKDF2WithHmacSHA1"; { secretKey = rfc2898.GetBytes(16); // builder.mKeyLength = 128; //Console.WriteLine("Key: " + ByteArrayToHexString(secretKey)); } return secretKey; }
This method derives a secret key using
PBKDF2WithHmacSHA1
with a key, salt, iterationcount and key length as input. The key (more precisely password) used here is a base64-encoded SHA1-hash fromMyCryptoClass.encryptionKey
provided byGetHashedKey()
(see below).Possible C#-Counterpart of the Java-method
hashTheKey(String key)
:private static string GetHashedKey() { string hashBase64 = String.Empty; using (SHA1Managed sha1 = new SHA1Managed()) // builder.mDigestAlgorithm = "SHA1"; { byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(encryptionKey)); // builder.mCharsetName = "UTF8"; hashBase64 = Base64ThirdPartAndroid(hash, true); //Console.WriteLine("Hash (base64): " + hashBase64); } return hashBase64; }
This method derives a
SHA1
-hash fromMyCryptoClass.encryptionKey
and base64-encodes that hash. For the base64-encoding the methodBase64ThirdPartAndroid(byte[] arr, bool withoutPadding)
(see below) is used.Possible C#-Counterpart of the Java-method
third.part.android.util.Base64.encodeToString(byte[] input, int flags)
( https://github.com/simbiose/Encryption/blob/master/Encryption/main/third/part/android/util/Base64.java):private static string Base64ThirdPartAndroid(byte[] arr, bool withoutPadding) { string base64String = System.Convert.ToBase64String(arr); if (withoutPadding) base64String = base64String.TrimEnd('='); // Remove trailing "="-characters base64String += "\n"; // Append LF (10) //Console.WriteLine("Array as base64 encoded string: " + base64String); return base64String; }
In the Java code
third.part.android.util.Base64.encodeToString(byte[] input, int flags)
is used withflags = Base64.NO_PADDING
which removes the "="-character at the end of the base64-encoded string. Additionally a line feed (LF,\n
, ASCII value: 10) is appended. If a Base64-Encoding is used which doesn't remove the "="-characters or without a terminating line feed, the decryption will fail since the hash is the base of the later generated secret key which have to match on the encryption and on the decryption side. To the best of my knowledge there is no Base64-encoding on the C# side with the necessary characteristics. However, if there is such an encoding you can use it of course.Add all three C#-counterparts to your
MyCryptoClass class
.Additionally (to the static field
encryptionKey
) add the static fieldsinitialisationVector
,salt
andsecretKey
to yourMyCryptoClass
-class and assign the following values for testing purposes:private static string encryptionKey = "A7zb534OPq59gU7q"; private static string salt = "JV5k9GoH"; private static byte[] initialisationVector = Encoding.UTF8.GetBytes("l4iG63jN9Dcg6537"); private static byte[] secretKey = GetSecretKey();
The type of the parameters corresponds to the type in the Java code (
encryptionKey
andsalt
are strings, theinitialisationVector
is a byte-array). The secret key generated byGetSecretKey()
is stored in the byte-arraysecretKey
.In your C#
DecryptText
- andEncryptText
-method setmyRijndael.Key
andmyRijndael.IV
tomyRijndael.Key = secretKey; myRijndael.IV = initialisationVector;
Test the modifications as follows:
With your Java
encrypt
-method encrypt the following plain text:Test: The quick brown fox jumps over the lazy dog...
using the key/salt/iv above with
mBuilder = Builder.getDefaultBuilder("A7zb534OPq59gU7q","JV5k9GoH","l4iG63jN9Dcg6537".getBytes("UTF-8"));
The encrypted text is:
mL4ajZtdRgD8CtGSfJGkT24Ebw4SrGUGKQI6bvBw1ziCO/J7SeLiyIw41zumTHMMD9GOYK+kR79CVcpoaHT9TQ==
Decrypting this using the C#
DecryptText
-method gives again the plain text. Below are two test cases:static void Main(string[] args) { // Test 1: Encrypted text from C# MyCryptoClass mcc = MyCryptoClass.Instance; string encryptedText = mcc.EncryptText("This is a plain text which needs to be encrypted..."); Console.WriteLine("Encrypted text (base64): " + encryptedText); string decryptedText = mcc.DecryptText(encryptedText); Console.WriteLine("Decrypted text: " + decryptedText); // Test 2: Encrypted text from Java string javaEncryptedText = "mL4ajZtdRgD8CtGSfJGkT24Ebw4SrGUGKQI6bvBw1ziCO/J7SeLiyIw41zumTHMMD9GOYK+kR79CVcpoaHT9TQ=="; Console.WriteLine("Encrypted text from Java (base64): " + javaEncryptedText); string javaDecryptedText = mcc.DecryptText(javaEncryptedText); Console.WriteLine("Decrypted text from Java: " + javaDecryptedText); }
回答2:
Following is Full C# class which i were able to work as it is like java class At the moment i have only checked encryption part
public sealed class MyCryptoClass
{
protected AesManaged myRijndael;
private static string encryptionKey = "MyKey";
private static string salt = "Mysalt";
private static byte[] initialisationVector = new byte[16];
//private static byte[] initialisationVector = Encoding.UTF8.GetBytes("l4iG63jN9Dcg6537");
private static byte[] secretKey = GetSecretKey();
// Singleton pattern used here with ensured thread safety
protected static readonly MyCryptoClass _instance = new MyCryptoClass();
public static MyCryptoClass Instance
{
get { return _instance; }
}
public MyCryptoClass()
{
}
public string DecryptText(string encryptedString)
{
using (myRijndael = new AesManaged())
{
myRijndael.Key = Convert.FromBase64String(encryptionKey);
myRijndael.IV = new byte[16];
myRijndael.Mode = CipherMode.CBC;
myRijndael.Padding = PaddingMode.PKCS7;
Byte[] ourEnc = Convert.FromBase64String(encryptedString);
string ourDec = DecryptStringFromBytes(ourEnc, myRijndael.Key, myRijndael.IV);
return ourDec;
}
}
public string EncryptText(string plainText)
{
using (myRijndael = new AesManaged())
{
myRijndael.Key = secretKey;
myRijndael.IV = initialisationVector;
myRijndael.Mode = CipherMode.CBC;
myRijndael.Padding = PaddingMode.PKCS7;
byte[] encrypted = EncryptStringToBytes(plainText, myRijndael.Key, myRijndael.IV);
string encString = Convert.ToBase64String(encrypted);
return encString;
}
}
protected byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
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;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
protected string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
{
// Check arguments.
if (cipherText == null || cipherText.Length <= 0)
throw new ArgumentNullException("cipherText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("Key");
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
public static void GenerateKeyAndIV()
{
// This code is only here for an example
AesManaged myRijndaelManaged = new AesManaged();
myRijndaelManaged.Mode = CipherMode.CBC;
myRijndaelManaged.Padding = PaddingMode.PKCS7;
myRijndaelManaged.GenerateIV();
myRijndaelManaged.GenerateKey();
string newKey = ByteArrayToHexString(myRijndaelManaged.Key);
string newinitVector = ByteArrayToHexString(myRijndaelManaged.IV);
}
protected static byte[] HexStringToByte(string hexString)
{
try
{
int bytesCount = (hexString.Length) / 2;
byte[] bytes = new byte[bytesCount];
for (int x = 0; x < bytesCount; ++x)
{
bytes[x] = Convert.ToByte(hexString.Substring(x * 2, 2), 16);
}
return bytes;
}
catch
{
throw;
}
}
public static string ByteArrayToHexString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
private static byte[] GetSecretKey()
{
string hashedKey = GetHashedKey();
byte[] saltBytes = Encoding.UTF8.GetBytes(salt); // builder.mCharsetName = "UTF8";
int iterations = 1; // builder.mIterationCount = 1
byte[] secretKey = null;
using (Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(hashedKey, saltBytes, iterations)) // builder.mSecretKeyType = "PBKDF2WithHmacSHA1";
{
secretKey = rfc2898.GetBytes(16); // builder.mKeyLength = 128;
//Console.WriteLine("Key: " + ByteArrayToHexString(secretKey));
}
return secretKey;
}
private static string GetHashedKey()
{
string hashBase64 = String.Empty;
using (SHA1Managed sha1 = new SHA1Managed()) // builder.mDigestAlgorithm = "SHA1";
{
byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(encryptionKey)); // builder.mCharsetName = "UTF8";
hashBase64 = Base64ThirdPartAndroid(hash, true);
//hashBase64 = Base64ThirdPartAndroid(hash, true);
//Console.WriteLine("Hash (base64): " + hashBase64);
}
return hashBase64;
}
private static string Base64ThirdPartAndroid(byte[] arr, bool withoutPadding)
{
string base64String = System.Convert.ToBase64String(arr);
if (withoutPadding) base64String = base64String.TrimEnd('='); // Remove trailing "="-characters
base64String += "\n"; // Append LF (10)
//Console.WriteLine("Array as base64 encoded string: " + base64String);
return base64String;
}
}
来源:https://stackoverflow.com/questions/53580563/c-sharp-encrypt-decrypt-from-java-aes-cbc-pkcs5padding