I found a solution to this problem here.
private byte[] toBytes(char[] chars) {
CharBuffer charBuffer = CharBuffer.wrap(chars);
ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
byte[] bytes = Arrays.copyOfRange(byteBuffer.array(),
byteBuffer.position(), byteBuffer.limit());
Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
return bytes;
}
char[] stringChars = "String".toCharArray();
byte[] stringBytes = toBytes(stringChars);
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(stringBytes);
String stringHash = new BigInteger(1, md.digest()).toString(16);
Arrays.fill(stringChars, '\u0000');
Arrays.fill(stringBytes, (byte) 0);
But it seems to have a bug, I can't figure out where or how it happens.
The problem is this part I think:
String hashedPass = new BigInteger(1, md.digest()).toString(16);
The output of above code gives for String:
String = "9a9cce201b492954f0b06abb081d0bb4";
Correct MD5 of above string = "0e67b8eb546c322eeb39153714162ceb",
The code above though gives = "e67b8eb546c322eeb39153714162ceb";
It seems leading zeros of a MD5 are missing.
You don't have to use BigInteger for this task, just write a method for converting byte arrays to hex strings.
static String hexEncode(byte [] data) {
StringBuilder hex = new StringBuilder();
for (byte b : data) hex.append(String.format("%02x", b));
return hex.toString();
}
String hash = hexEncode(md.digest());
Related
I was given this .NET code and I need to convert it to Java. I have found a bunch of articles but nothing I try works.
public string EncryptNVal(string vVal, string accountNumber, string sharedSecret)
{
byte[] IV = Convert.FromBase64String(vVal);
byte[] Key = Convert.FromBase64String(sharedSecret);
SymmetricAlgorithm sa = Rijndael.Create();
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, sa.CreateEncryptor(Key, IV),
CryptoStreamMode.Write))
{
byte[] data = Encoding.UTF8.GetBytes(accountNumber);
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
ms.Position = 0;
return Convert.ToBase64String(ms.ToArray());
}
}
This is some of my attempt which you can guess throws exceptions.
public String EncryptNVal(String vVal, String accountNumber, String sharedSecret) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] IV = Base64.decodeBase64(vVal);
byte[] Key =Base64.decodeBase64(sharedSecret);
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(Base64.decodeBase64(sharedSecret).toString().toCharArray());
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = factory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(IV,salt,1000);
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] ciphertext = cipher.doFinal(accountNumber.getBytes(StandardCharsets.UTF_8));
return Base64.encodeBase64String(ciphertext);
In the same project I have this code which I think I got correct, but any confirmation on that would be helpful as well.
.NET code I was given
public string GetMd5Hash(string input)
{
using (var md5Hash = MD5.Create())
{
// Convert the input string to a byte array and compute the hash.
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
var sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
foreach (var t in data)
{
sBuilder.Append(t.ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
}
What I wrote in Java:
public String GetHash(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] inputBytes = input.getBytes("UTF-8");
md.update(inputBytes);
byte inputHash[] = md.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < inputHash.length; i++) {
hexString.append(Integer.toHexString(0xFF & inputHash[i]));
}
return hexString.toString();
}
On a side note I can use either MD5 or SHA256, the .NET code was using MD5, so I was trying to follow that since my knowledge of encryption is about null. I am willing to use the SHA256 if someone can give me good advice.
I took me some time, and a lot of errors to figure this out, but here's a java method which yields exactly the same output as your C# method.
I'm no expert at all in crypto stuff, but this seems to do the work.
public static String EncryptNVal(String vVal, String accountNumber, String sharedSecret){
try {
byte[] vValAsBytes = java.util.Base64.getDecoder().decode(vVal);
byte[] sharedSecretAsBytes = java.util.Base64.getDecoder().decode(sharedSecret);
byte[] accountNumberAsBytes = accountNumber.getBytes();
SecretKeySpec key = new SecretKeySpec(sharedSecretAsBytes, "AES");
IvParameterSpec iv = new IvParameterSpec(vValAsBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] output = cipher.doFinal(accountNumberAsBytes);
String signature = java.util.Base64.getEncoder().encodeToString(output);
return signature;
}
catch(Exception e){
e.printStackTrace();
return "<dummy>";
}
}
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
A few additional info :
Rijndael is not AES, in the sense that it's not implementing the actual AES standard. If this is for production use, I'd suggest moving to actual AES (take a look at this link for info).
AES/CBC/PKCS5Padding is the closest match I could find for C#'s Rijndael . Although C# seems to use PKCS7, this doesn't seem available for Java (at least not on my system). PKCS5Padding yields the same result, but it will probably not work in 100% of the cases.
It only required trial and error, no deep knowledge of either C# or Java. You'll probably face similar challenges (or related problems which will be induced by your particular use case), and I'd suggest you apply a similar workflow as mine, when it comes to translating from a language to another :
Use and abuse of printing capabilities, to ensure all steps of both codes have the same state
Arrange the code with the same logical flow on both sides
Thoroughly read the documentation of your source function that you want to translate
I have got to make the same function in Java and C#, but the result are not the same.
My code in C# :
string xmlString = System.IO.File.ReadAllText(#"crc.xml");
byte[] bytes = Encoding.ASCII.GetBytes(xmlString);
// step 1, calculate MD5 hash from input
MD5 md5 = System.Security.Cryptography.MD5.Create();
byte[] hash = md5.ComputeHash(bytes);
// step 2, convert byte array to hex string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
Console.WriteLine(sb.ToString());
And my code in Java :
string xmlstring = Files.readString(Paths.get("crc.xml"));
MessageDigest m = MessageDigest.getInstance("MD5");
byte[] digest = m.digest(xmlstring.getbytes());
String hash = new BigInteger(1, digest).toString(16);
System.out.println(hash);
In C# I have this result :
F5F8B2F361FEA6EA30F24BEBAA5BDE3A
But in Java I have this result :
8fb40aad49fbf796b82a2faa11cda764
What I'm doing wrong?
Codebrane say it. Use
byte[] bytes = Encoding.UFT8.GetBytes(xmlString);
instead of
byte[] bytes = Encoding.ASCII.GetBytes(xmlString);
Trying to do AES decrypt the data using BounceCastle, getting mac check in GCM failed error at line: output = cipher.DoFinal(cipherData);
https://github.com/psraju1/CSharpApplePayDecrypter for full code
Error:
mac check in GCM failed
BouncyCastle.Crypto
at Org.BouncyCastle.Crypto.Modes.GcmBlockCipher.DoFinal(Byte[] output, Int32 outOff)
at Org.BouncyCastle.Crypto.BufferedAeadBlockCipher.DoFinal(Byte[] output, Int32 outOff)
at Org.BouncyCastle.Crypto.BufferedAeadBlockCipher.DoFinal(Byte[] input, Int32 inOff, Int32 inLen)
at Org.BouncyCastle.Crypto.BufferedCipherBase.DoFinal(Byte[] input)
at ApplePayDecrypter.ApplePay.DoDecrypt(Byte[] cipherData, Byte[] encryptionKeyBytes) in ApplePayDecrypter.cs:line 107
Code:
protected byte[] RestoreSymmertricKey(byte[] sharedSecretBytes)
{
byte[] merchantIdentifier = GetHashSha256Bytes("");//applePayRequest.MerchantIdentifier);
ConcatenationKdfGenerator generator = new ConcatenationKdfGenerator(new Sha256Digest());
byte[] COUNTER = { 0x00, 0x00, 0x00, 0x01 };
byte[] algorithmIdBytes = Encoding.UTF8.GetBytes((char)0x0d + "id-aes256-GCM");
byte[] partyUInfoBytes = Encoding.UTF8.GetBytes("Apple");
byte[] partyVInfoBytes = merchantIdentifier;
byte[] otherInfoBytes = Combine(Combine(algorithmIdBytes, partyUInfoBytes), COUNTER);//, partyVInfoBytes);
generator.Init(new KdfParameters(sharedSecretBytes, otherInfoBytes));
byte[] encryptionKeyBytes = new byte[16];
generator.GenerateBytes(encryptionKeyBytes, 0, encryptionKeyBytes.Length);
return encryptionKeyBytes;
}
private byte[] DoDecrypt(byte[] cipherData, byte[] encryptionKeyBytes)
{
byte[] output;
try
{
KeyParameter keyparam = ParameterUtilities.CreateKeyParameter("AES", encryptionKeyBytes);
ParametersWithIV parameters = new ParametersWithIV(keyparam, symmetricIv);
IBufferedCipher cipher = GetCipher();
cipher.Init(false, parameters);
try
{
output = cipher.DoFinal(cipherData);
}
catch (Exception ex)
{
throw new ApplicationException("Invalid Data");
}
}
catch (Exception ex)
{
throw new ApplicationException("There was an error occured when decrypting message.");
}
return output;
}
public IBufferedCipher GetCipher()
{
return CipherUtilities.GetCipher("AES/GCM/NoPadding");
}
private static byte[] GetHashSha256Bytes(string text)
{
byte[] bytes = Encoding.UTF8.GetBytes(text);
SHA256Managed hashstring = new SHA256Managed();
byte[] hash = hashstring.ComputeHash(bytes);
return hash;
}
protected static byte[] Combine(byte[] first, byte[] second)
{
byte[] ret = new byte[first.Length + second.Length];
Buffer.BlockCopy(first, 0, ret, 0, first.Length);
Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
return ret;
}
Actually, I'm trying to decrypt this for ApplePay, and converting sample Java code to C#. Let me know, if you want to take a look at JAVA code
Here is the full code in JAVA & C#. Please check it.
https://github.com/psraju1/CSharpApplePayDecrypter
I had the same problem, so I forked the repo above, made the necessary changes, cleaned it up and pushed it to GitHub.
https://github.com/fscopel/CSharpApplePayDecrypter
Happy Coding!
had the same problem. Please try not to hash the merchant identifier. It is already hashed. And ignore the first two bytes of it, before set to partyVInfo. When you get merchantIdentifier from certifcate there is ".#" at the beginning of the hash. This must be deleted
byte[] partyVInfo = ExtractMIdentifier();
private byte[] ExtractMIdentifier()
{
X509Certificate2 merchantCertificate = InflateCertificate();
byte[] merchantIdentifierTlv = merchantCertificate.Extensions["1.2.840.113635.100.6.32"].RawData;
byte[] merchantIdentifier = new byte[64];
Buffer.BlockCopy(merchantIdentifierTlv, 2, merchantIdentifier, 0, 64);
return Hex.Decode(Encoding.ASCII.GetString(merchantIdentifier));
}
Party V Info
The SHA-256 hash of the merchant identifier. This value is a fixed-length bit string.
I don't use SHA-256 hash and deleted the first two bytes of the merchant identifier. Now it works.
After following Lou's advice, I needed two more changes to get it to work.
I got rid of COUNTER and used partyVInfoByes
I changed encryptionKeyBytes to be an array of 32 instead of 16 bytes, since it's 256 bit ECC (32 * 8 = 256)
RestoreSymmetricKey now looks like this:
protected byte[] RestoreSymmertricKey(byte[] sharedSecretBytes)
{
byte[] merchantIdentifier = ExtractMIdentifier();
ConcatenationKdfGenerator generator = new ConcatenationKdfGenerator(new Sha256Digest());
byte[] algorithmIdBytes = Encoding.UTF8.GetBytes((char)0x0d + "id-aes256-GCM");
byte[] partyUInfoBytes = Encoding.UTF8.GetBytes("Apple");
byte[] partyVInfoBytes = merchantIdentifier;
byte[] otherInfoBytes = Combine(Combine(algorithmIdBytes, partyUInfoBytes), partyVInfoBytes);
generator.Init(new KdfParameters(sharedSecretBytes, otherInfoBytes));
byte[] encryptionKeyBytes = new byte[32];
generator.GenerateBytes(encryptionKeyBytes, 0, encryptionKeyBytes.Length);
return encryptionKeyBytes;
}
public static byte[] decryptByte(byte[] blahh, byte[] keyExample) throws Exception
{
Cipher cipher = null;
try
{
cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(Base64.decodeBase64(blah));
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
String keyExample = "99112277445566778899AABBCCDDEEFF0123456789ABCDEF0123456789ABCDEF";
byte[] key = keyExample.getBytes();
byte[] barrayMessage = {123,45,55,23,64,21,65};
byte[] result = decryptByte(barrayMessage, key);
Exception thrown: java.security.InvalidKeyException: Invalid AES key length: 64 bytes
When you call String.getBytes() (JDK documentation) you encodes characters of the given string into a sequence of bytes using the platform's default charset.
What you are actually need to do is to convert each hexadecimal (also base 16) number (represented by two characters from 0 to 9 and A to F e.g. 1A, 99, etc.) into its corresponding numerical (byte) value e.g. "FF" -> -1 byte.
Sample code is as follows:
import static java.lang.Character.digit;
...
private static byte[] stringToBytes(String input) {
int length = input.length();
byte[] output = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
output[i / 2] = (byte) ((digit(input.charAt(i), 16) << 4) | digit(input.charAt(i+1), 16));
}
return output;
}
...
String keyExample = "99112277445566778899AABBCCDDEEFF0123456789ABCDEF0123456789ABCDEF";
byte[] key = stringToBytes(keyExample);
byte[] barrayMessage = {123,45,55,23,64,21,65};
byte[] result = decryptByte(barrayMessage, key);
Please bear in mind that because we convert each two characters into a single byte, the proposed method assumes your input will have even number of characters (also the input is not null and empty).
If that method is going to be used internally that form is acceptable but if you make it as a part of library visible to others, it would be good to put some checks and throw exception on invalid input.
You should try and decode your key using a hexadecimal decoder instead of calling getBytes().
I have some code that is working correctly in Java but when I try to use it in Android it is having problems.
I am attempting to encrypt an SMS text message with the Blowfish algorithm.
The problem with this code (on android) is that it will not accept the byte[] and will not decrypt the message.
SENDING THE SMS
sMessage = "hello this is a message"
byte[] byteArray = EncryptBlowfish(sMessage);
//Convert the byte[] into a String which can be sent over SMS
StringBuffer strb = new StringBuffer();
for( int x = 0; x<byteArray.length; x++){
strb.append(byteArray[x]).append(",");
}//for loop
sMessage = strb.toString();
(sMessage is then sent via SMS)
RECIVING THE SMS
//Convert the String back to a byte[] which can be decrypted
String[] strArray = sMessage.split(",");
byte[] byteArray = new byte[strArray.length];
int hold;
for (int x = 0; x < strArray.length; x++) {
hold = Integer.parseInt(strArray[x]);
byteArray[x] = (byte) hold;
}//for loop
sMessage = DecryptBlowfish(byteArray);
Encryption Method
public static byte[] EncryptBlowfish(String msg){
byte[] encrypted =null;
try {
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, secretkey);
encrypted = cipher.doFinal(msg.getBytes());
} catch (){ //NoSuchAlgorithmException, NoSuchPaddingException..etc
}
return encrypted;
}
Decryption Method
public static String DecryptBlowfish(byte[] msg){
byte[] decrypted =null;
try {
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.DECRYPT_MODE, secretkey);
decrypted = cipher.doFinal(msg);
} catch (){ //NoSuchAlgorithmException, NoSuchPaddingException..etc
}
return decrypted;
}
The message is being encrypted, this creates a byte[], I have then converted the byte[] to a string, the string's output will look something like this...
46,77,52,11,-108,91,-106,88,-81,-43,14,111,-118,-128,-92,-50,69,-44,100,-94,71,92,-49,116,
this output is then sent over SMS. The string is then convert back into a byte[]
but this byte array is not decrypting.
Questions:
Why would this code work in a Java app, but not Android?
Is there a way of making this work in Android?
Is there a better method of converting the byte[] to a String and back.
(Please comment if anymore information is require, Thanks)
I think the answer involves what the default character encoding is on Android vs standard Java. What happens if you specify the character encoding using msg.getBytes(Charset c), and for decoding new String(byte [], Charset c).
Example:
// String => byte []
byte[] bytes = message.getBytes(Charset.forName("ISO-8859-1"));
// byte [] => String
String foo = new String(bytes, Charset.forName("ISO-8859-1"));
You can find what character sets are available from:
for (String c : Charset.availableCharsets().keySet()) {
System.out.println(c);
}
I think there is a problem when you make byte -> string -> byte conversion. Try to send an unencrypted string and retrieve it and check if it is correct.
You should probably specify the encoding at each step.
To convert from a byte array to a string use this
Base64.encodeToString(byteArray, Base64.NO_WRAP);
To convert from a string to a byte array use this
Base64.decode(string, Base64.DEFAULT);
The Base64 class is in the android.util package.