Despite I found lot's of posts talking about the same issue I couldn't fix my issue.
I am using a c# server and a java client (in the future will be android). The curious stuff is that I am sending/receiving many json strings longer and more complex and just in the shortest and easiest fails.
This is the code for the client:
private static byte[] asegurarCapacidad(byte[] inicial, int tamano){
if(inicial.length<tamano){
return Arrays.copyOf(inicial, tamano);
}
return inicial;
}
private static String leer(Socket s){
byte[] buffer=new byte[4092];
byte[] bufferFinal = new byte[8092];
int leido=0;
int posicion=0;
String salida = null;
try {
SecretKey key = new SecretKeySpec("1212121212121212".getBytes("UTF-8"),"AES");
DataInputStream dis=new DataInputStream(s.getInputStream());
while((leido=dis.read(buffer))>0){
bufferFinal=asegurarCapacidad(bufferFinal,posicion+leido);
System.arraycopy(buffer, 0, bufferFinal, posicion, leido);
posicion+=leido;
}
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key,iv);
salida=decodificar(cipher.doFinal(bufferFinal));
} catch (Throwable e) {
System.out.println("CLIENTE: ERROR AL LEER: "+e);
e.printStackTrace();
}
return salida;
}
And this is the code of the server:
private static byte[] codificar(string mensaje)
{
return System.Text.Encoding.UTF8.GetBytes(mensaje);
}
private static void escribir(Socket s, string mensaje)
{
byte[] buffer = new byte[mensaje.Length];
buffer = codificar(mensaje);
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.KeySize = 128;
aes.BlockSize = 128;
aes.Key = bytKey;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.IV = iv;
ICryptoTransform AESEncrypt = aes.CreateEncryptor();
buffer = AESEncrypt.TransformFinalBlock(buffer, 0, buffer.Length);
s.Send(buffer);
}
Any suggestion will be welcome.
You have a buffer of 8092 bytes (bufferFinal). Suppose the input stream contains only 1000 bytes. Your code reads these 1000 bytes and calls
asegurarCapacidad(bufferFinal, posicion + leido);
where posicion is 0 and leido is 1000.
The method asegurarCapacidad() does the following:
if (inicial.length < tamano) {
return Arrays.copyOf(inicial, tamano);
}
return inicial;
where inicial.length is 8092 and tamano is 1000. So it returns the buffer unmodified.
So you end up with a buffer of 8092 bytes containing the 1000 bytes read from the input stream, and 7092 bytes set to 0. And that's what you're passing to the Cipher, instead of only passing the 1000 first bytes.
It works when you have a larger text as input, because the asegurarCapacidad() ends up returning a buffer larger than 8092 bytes containing only the encrypted bytes. But your code is inefficient (it reallocates a new array every time), and could be much simpler:
byte[] buffer = new byte[8092];
try {
InputStream in = s.getInputStream();
SecretKey key = new SecretKeySpec("1212121212121212".getBytes("UTF-8"),"AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
while ((leido = in.read(buffer)) > 0) {
cipher.update(buffer, 0, leido);
}
salida = decodificar(cipher.doFinal());
Related
I'd like to encrypt a string multiple times. But I don't know why I'm ending with an empty byte array.
One public key is ok but adding another one returns an empty result.. Does anyone know why ?
private static byte[] encrypt(LinkedList<PublicKey> keys, byte[] input) throws Exception {
System.out.println("Input length : " + input.length);
if (keys.isEmpty()) {
return input;
}
PublicKey publicKey = keys.poll();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
try (CipherOutputStream cipherOutputStream = new CipherOutputStream(byteArrayOutputStream, cipher)) {
cipherOutputStream.write(input);
}
byteArrayOutputStream.flush();
byteArrayOutputStream.close();
return encrypt(keys, byteArrayOutputStream.toByteArray());
}
public static void main(String[] args) throws Exception {
KeyPair pair1 = createPair();
KeyPair pair2 = createPair();
LinkedList<PublicKey> keys = new LinkedList<>();
keys.add(pair1.getPublic());
keys.add(pair2.getPublic());
byte[] result = encrypt(keys, "Just testing".getBytes(Charset.forName("UTF-8")));
System.out.println(new String(result, Charset.forName("UTF-8")));
}
public static KeyPair createPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048);
return keyPairGen.generateKeyPair();
}
The output is
Input length : 12
Input length : 256
Input length : 0
After Topaco' answer.. a working version is :
private static BufferedInputStream encryptS(LinkedList<PublicKey> keys, BufferedInputStream inputStream) throws Exception {
if (keys.isEmpty()) {
return inputStream;
}
PublicKey publicKey = keys.poll();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int currentPos = 0;
while (inputStream.available() > 0) {
int sizeToRead = Math.min(inputStream.available(), 245);
try (CipherOutputStream cipherOutputStream = new CipherOutputStream(byteArrayOutputStream, cipher)) {
byte[] array = new byte[sizeToRead];
inputStream.read(array, 0, sizeToRead);
cipherOutputStream.write(array);
currentPos += sizeToRead;
}
}
byteArrayOutputStream.close();
return encryptS(keys, new BufferedInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())));
}
For RSA, the following must be taken into account:
The length of the message plus padding must not exceed the key length (= size of the modulus) [0], [1], [2], [3]. Padding means that additional bytes are added to the message according to a certain scheme [4].
The length of the ciphertext corresponds to the key length (=size of the modulus), [5].
This means that already after the first encryption the maximum allowed length is reached. Thus, without padding the maximum length is not exceeded, with padding it is exceeded.
Creating the cipher instance with
Cipher.getInstance("RSA")
corresponds to
Cipher.getInstance("RSA/ECB/PKCS1Padding")
for the SunJCE-Provider ([6], [7]), i.e. PKCS1 v1.5 padding is used with a padding of at least 11 characters, so that with a key size of 256 bytes the maximum size of the message must not exceed 245 bytes.
This is the reason why the recursive encryption in the current code doesn't work. If the cipher instance is created with
Cipher.getInstance("RSA/ECB/NoPadding")
(no padding used), the current code works.
However, for security reasons a padding must always be used in practice!
I don't know why an error is coming up.
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
I understand that this error occurs when the incorrect key is used during the decryption. However, if you look at the test results result below, you can see that both C# and Java are the same (Key, IV, Salt is Base64 encoded).
C# Test Result
Java Test Result
It's the same!(Key, IV, Salt)
But the current BadpaddingException error is generated. What could be the problem?
I am attaching my source file.
C# (Encryption)
class AES {
private readonly static string keyStr = "This is Key";
private readonly static string vector = "This is Vector";
public static Rfc2898DeriveBytes MakeKey(string password){
byte[] keyBytes = System.Text.Encoding.UTF8.GetBytes(password);
byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(keyBytes, saltBytes, 65536);
return result;
}
public static Rfc2898DeriveBytes MakeVector(string vector){
byte[] vectorBytes = System.Text.Encoding.UTF8.GetBytes(vector);
byte[] saltBytes = SHA512.Create().ComputeHash(vectorBytes);
Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(vectorBytes, saltBytes, 65536);
return result;
}
public static void Encrypt(String inputFile, String outputFile) {
using (RijndaelManaged aes = new RijndaelManaged()){
//Create Key and Vector
Rfc2898DeriveBytes key = AES.MakeKey(AES.keyStr);
Rfc2898DeriveBytes vector = AES.MakeVector(AES.vector);
//AES256
aes.BlockSize = 128;
aes.KeySize = 256;
// It is equal in java
// Cipher _Cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = key.GetBytes(32); //256bit key
aes.IV = vector.GetBytes(16); //128bit block size
//processing Encrypt
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] encrypted;
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
byte[] inputBytes = File.ReadAllBytes(inputFile);
csEncrypt.Write(inputBytes, 0, inputBytes.Length);
}
encrypted = msEncrypt.ToArray();
}
string encodedString = Convert.ToBase64String(encrypted);
File.WriteAllText(outputFile, encodedString);
}
}
}
Java (Decryption)
public class AES256File {
private static final String algorithm = "AES";
private static final String blockNPadding = algorithm+"/CBC/PKCS5Padding";
private static final String password = "This is Key";
private static final String IV = "This is Vector";
private static IvParameterSpec ivSpec;
private static Key keySpec;
public static void MakeKey(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] keyBytes = password.getBytes("UTF-8");
// C# : byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
byte[] saltBytes = digest.digest(keyBytes);
//256bit
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 65536, 256);
Key secretKey = factory.generateSecret(pbeKeySpec);
byte[] key = new byte[32];
System.arraycopy(secretKey.getEncoded(), 0, key, 0, 32);
SecretKeySpec secret = new SecretKeySpec(key, "AES");
setKeySpec(secret);
}
public static void MakeVector(String IV) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] vectorBytes = IV.getBytes("UTF-8");
byte[] saltBytes = digest.digest(vectorBytes);
// 128bit
PBEKeySpec pbeKeySpec = new PBEKeySpec(IV.toCharArray(), saltBytes, 65536, 128);
Key secretIV = factory.generateSecret(pbeKeySpec);
byte[] iv = new byte[16];
System.arraycopy(secretIV.getEncoded(), 0, iv, 0, 16);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
setIvSpec(ivSpec);
}
public void decrypt(File source, File dest) throws Exception {
Cipher c = Cipher.getInstance(blockNPadding);
c.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
fileProcessing(source, dest, c);
}
public void fileProcessing(File source, File dest, Cipher c) throws Exception{
InputStream input = null;
OutputStream output = null;
try{
input = new BufferedInputStream(new FileInputStream(source));
output = new BufferedOutputStream(new FileOutputStream(dest));
byte[] buffer = new byte[input.available()];
int read = -1;
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
byte[] deryptedBytes = c.doFinal(buffer); // -----------------------> Error!! Showing!
byte[] decodedBytes = Base64.getDecoder().decode(deryptedBytes);
String decodeString = new String(decodedBytes, "UTF-8");
decodedBytes = decodeString.getBytes(StandardCharsets.UTF_8);
output.write(decodedBytes);
}finally{
if(output != null){
try{output.close();}catch(IOException e){}
}
if(input != null){
try{input.close();}catch(IOException e){}
}
}
}
I have verified as below.
Verification Key and IV in C#
//Key Verification
var salt = Convert.ToBase64String(saltBytes);
Console.Write("Salt Result : ");
Console.WriteLine(salt);
var result_test = Convert.ToBase64String(result.GetBytes(32));
Console.Write("Key Test Result: ");
Console.WriteLine(result_test);
//IV Verification (Salt is Using same code)
var result_test = Convert.ToBase64String(result.GetBytes(16));
Console.Write("IV Test Result: ");
Console.WriteLine(result_test);
Verification Key and IV in Java
//Key Verification
/* print Salt */
String base64 = Base64.getEncoder().encodeToString(saltBytes);
System.out.println("Salt Result : " + base64);
/* print Key */
String result_test = Base64.getEncoder().encodeToString(key);
System.out.println("Key Test Result : " + result_test);
/* print generated Key */
System.out.println("Secret Key Result : " + Base64.getEncoder().encodeToString(secret.getEncoded()));
//IV Verification (Salt is Using same code)
/* print IV */
String result_test = Base64.getEncoder().encodeToString(iv);
System.out.println("IV Test Result : " + result_test);
/* print generated IV */
System.out.println("IV Result : " + Base64.getEncoder().encodeToString(ivSpec.getIV()));
Updated
c# .netframework 4.5 / Java8 modified what #Topaco said and confirmed that it worked well.
I want to say thank you very much to #Topaco and #Gusto2, and I'm going to make changes to the parts that have been modified in security, just as #Gusto2 said!
1) In the C# Encrypt-method the plain text is encrypted first and then Base64-encoded. Thus, in the decryption process the data must be Base64-decoded first and then decrypted. Currently this is handled in the wrong order i.e. the data are decrypted first and then decoded. Therefore, in the Java fileProcessing-method replace
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
with
while((read = input.read(buffer)) != -1) {
byte[] bufferEncoded = buffer;
if (read != buffer.length) {
bufferEncoded = Arrays.copyOf(buffer, read);
}
byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
output.write(c.update(bufferDecoded));
}
2) It's not necessary to pass buffer (or bufferDecoded) to the doFinal-method, since that was already done in the update-method. Thus,
byte[] deryptedBytes = c.doFinal(buffer);
must be replaced with
output.write(c.doFinal());
3) Since the Base64-decoding is already done in 1) in the try-block all lines following the doFinal-statement have to be removed. Overall, this results in
try {
input = new BufferedInputStream(new FileInputStream(source));
output = new BufferedOutputStream(new FileOutputStream(dest));
byte[] buffer = new byte[input.available()];
int read = -1;
while((read = input.read(buffer)) != -1) {
byte[] bufferEncoded = buffer;
if (read != buffer.length) {
bufferEncoded = Arrays.copyOf(buffer, read);
}
byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
output.write(c.update(bufferDecoded));
}
output.write(c.doFinal());
}
4) The size of the buffer has to be a multiple of 4 in order to ensure a proper Base64-decoding. Thus, it's more reliable to replace
byte[] buffer = new byte[input.available()];
with
byte[] buffer = new byte[4 * (input.available() / 4)];
As long as the data are read in one chunk (which is not guaranteed, see e.g. https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html#available()) there is no problem. However, if the data are read in several chunks it's important to read a multiple of 4 bytes, otherwise the Base64-decoding will fail. That can be easily proved by using a buffer size which isn't a multiple of 4. This point must also be considered if the buffer size is explicitly defined with regard to larger files.
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
byte[] deryptedBytes = c.doFinal(buffer)
you are decrypting the input to a file, then you are using the same cipher instance to decrypt the the last read chunk (again) into a separate array not to the file
quick fix:
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
output.write(c.doFinal()); // write the padded block
if you want to create and print a decrypted String, you need to create a new Cipher instance (or maybe it will be enough to reinitialize the instance, I am not sure) assuming the buffer contains the whole input
c.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// assuming the buffer contains the whole input again
byte[] deryptedBytes = c.doFinal(buffer); // decrypting the whole file again
correct approach:
IV is used to securely reuse the same encryption key for multiple encryptions. So if your key is not random, you should generate new random IV for each encryption (and pass the IV along the ciphertext, most often prepended). Otherwise the encryption is not semantically secure and you may create opening for the two pad attack. So deriving IV from the key may not be very secure.
I advice to use any MAC (authentication code) passed along the ciphertext to ensure integrity (e.g. HMAC)
you are still reading all the file input fully into memory, what would not work for REALLY LARGE files. You may initialize the buffer to an arbitrary length (a few MB?) and process the input file as chunked
I'm new to encryption/compression in Java and I'm working on a test project where the goal is to compress and then encrypt files via a buffered input in Java. At no point should the file be stored on disk in a non-encrypted format, therefore I want to do the compression and encryption solely on a buffer until the file is fully written.
So the progression would be: read part of file into memory (buffer, 1024 bytes) -> compress (~32 bytes)-> encrypt -> output to disk -> repeat until entire file is written
The issue I'm facing is that once I perform the reverse operations to read the compressed/encrypted file back, only part of the data is there.
To accomplish my goal, I've been combining the Inflater/Deflater classes and a block cipher with AES 256 encryption.
Encryption setup:
byte[] randomSalt = new byte[8];
SecureRandom secRand = new SecureRandom();
secRand.nextBytes(randomSalt);
String randomPassword = new BigInteger(130, secRand).toString(32);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(randomPassword.toCharArray(), randomSalt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
Getting input / writing output:
BufferedInputStream bufferedInput = new BufferedInputStream(
new FileInputStream("file.txt"));
BufferedOutputStream bufferedOutput = new BufferedOutputStream(
new FileOutputStream("encrypted file"));
byte[] buffer = new byte[1024];
try {
while (bufferedInput.read(buffer) != -1) {
byte[] encryptedBuffer = cipher.doFinal(compress(buffer));
bufferedOutput.write(encryptedBuffer);
bufferedOutput.flush();
}
} catch (Exception e) {
//snip
} finally {
bufferedInput.close();
bufferedOutput.close();
}
Compress method:
public static byte[] compress(byte[] data) throws IOException{
Deflater deflater = new Deflater();
deflater.setInput(data);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
deflater.finish();
byte[] buffer = new byte[1024];
while(!deflater.finished()){
int count = deflater.deflate(buffer);
outputStream.write(buffer, 0, count);
}
outputStream.close();
return outputStream.toByteArray();
}
What can I do to be able to compress and encrypt a file 1KB at a time and get the file back in its entirety when I perform the reverse operations?
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;
}
In a larger application doing other things - I need to encrypt and decrypt a file. So I have been looking around and have implemented these two core functions that basically use RSA keys to wrap a random AES key that encrypts a file. The symmetric key and iv are written to the start of the file.
I'm getting an exception ("javax.crypto.BadPaddingException: Decryption error") in the decrypt function portion of below. On the unpackKeyandIV line -- the doFinal. Specifically this line is the Exception point:
Object[] keyIv = unpackKeyAndIV(xCipher.doFinal(keyBlock));
I've checked and remade the RSA key pairs. I've also checked the save/load of the keyBlock.
My gut is the problem has something to do with how I write/read the keyBlock --- or encoding perhaps?
One goal is to keep the RSA/AES instance as generic as possible so as not to need Bouncy Castle or extra Java security unlimited strength extensions.
Any thoughts on where I might be going wrong.
Thanks in advance.
[Final update: This code below is working. Error was passing in a corrupted privKey]
// RSA_INSTANCE = "RSA";
// ASSYM_CRYPTO_STR = 1024;
// SYM_CRYPTO_STR = 128;
// SYM_CRYPTO = "AES";
// AES_INSTANCE = "AES/CTR/NoPadding";
//
// File in = plain input file
// File out = encrypted output file
// Key pubKey = public Key (that wraps a random AES key)
public static void encryptFile(File in, File out, Key pubKey) throws Exception {
FileInputStream fin;
FileOutputStream fout;
int nread = 0;
byte[] inbuf = new byte[1024];
fout = new FileOutputStream(out);
fin = new FileInputStream(in);
SecureRandom random = new SecureRandom();
// symmetric wrapping
Key sKey = createKeyForAES(Config.SYM_CRYPTO_STR, random);
IvParameterSpec sIvSpec = createCtrIvForAES(0, random);
// encrypt symmetric key with RSA/pub key
Cipher xCipher = Cipher.getInstance(Config.RSA_INSTANCE);
xCipher.init(Cipher.ENCRYPT_MODE, pubKey, random);
byte[] keyBlock = xCipher.doFinal(packKeyAndIv(sKey, sIvSpec));
fout.write(keyBlock);
// encrypt data with symmetric key
Cipher sCipher = Cipher.getInstance(Config.AES_INSTANCE);
sCipher.init(Cipher.ENCRYPT_MODE, sKey, sIvSpec);
// Now read our file and encrypt it.
while((nread = fin.read(inbuf)) > 0) {
fout.write(sCipher.update(inbuf, 0, nread)); // cannot be null, by construction
}
// NB doFinal() cannot return null, but can return a zero-length array, which is benign below.
fout.write(sCipher.doFinal());
fout.flush();
fin.close();
fout.close();
}
// Decrypt File
public static void decryptFile(File in, File out, Key privKey) throws Exception {
FileInputStream fin;
FileOutputStream fout;
int nread = 0;
byte[] inbuf = new byte[1024];
fout = new FileOutputStream(out);
fin = new FileInputStream(in);
byte[] keyBlock = new byte[128];
nread = fin.read(keyBlock);
Cipher xCipher = Cipher.getInstance(Config.RSA_INSTANCE);
Cipher sCipher = Cipher.getInstance(Config.AES_INSTANCE);
// symmetric key/iv unwrapping step
xCipher.init(Cipher.DECRYPT_MODE, privKey);
Object[] keyIv = unpackKeyAndIV(xCipher.doFinal(keyBlock));
// decryption step
sCipher.init(Cipher.DECRYPT_MODE, (Key)keyIv[0], (IvParameterSpec)keyIv[1]);
while((nread = fin.read(inbuf)) >0) {
fout.write(sCipher.update(inbuf,0,nread));
}
fout.write(sCipher.doFinal());
fout.flush();
fin.close();
fout.close();
}
public static byte[] packKeyAndIv(Key key, IvParameterSpec ivSpec) throws IOException {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
bOut.write(ivSpec.getIV());
bOut.write(key.getEncoded());
return bOut.toByteArray();
}
public static Object[] unpackKeyAndIV(byte[] data) {
byte[] keyD = new byte[16];
byte[] iv = new byte[data.length - 16];
return new Object[] {
new SecretKeySpec(data, 16, data.length - 16, "AES"),
new IvParameterSpec(data, 0, 16)
};
}
Per edits and comments. Error was a corrupted privKey being passed into the decrypt function. Above code works fine.
try adding the following under your constructor -
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());