I'm trying to encrypt and decrypt plain text with AES("AES/CBC/PKCS7Padding") with Cipher.
Made secretKey by KeyGenerator and encrypt/decrypt with it.
This works well at first.
However, if plain text is a large string(such as 183244 characters), part of it is decrypted and not all of it.
"part of it" means
If plain text is "abcdefghijklmn", decrypted text comes like "abcdefgh"
(this is an example. actually this happens only with too long characters)
Code
// get key from Android kyestore
SecretKey secretKey = (SecretKey)mKeyStore.getKey(KEY_ALIAS, null);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
IvParameterSpec ivParams = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
//
// Encrypt
//
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
cipherOutputStream.write(plainString.getBytes("UTF-8"));
cipherOutputStream.close();
String encryptedString = Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
//
// Decrypt
//
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivParams);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.decode(encryptedString, Base64.DEFAULT));
CipherInputStream cipherInputStream = new CipherInputStream(byteArrayInputStream, decryptCipher);
outputStream = new ByteArrayOutputStream();
int b;
while ((b = cipherInputStream.read()) != -1) {
outputStream.write(b);
}
outputStream.close();
String decryptedString = outputStream.toString("UTF-8");
//
// results. decrypted string is part of source string and short.
//
int sourceStringLength = plainString.length(); // is 183244;
int decryptedStringLength = decryptedString.length()); // is UNEXPECTED 68553;
Could you please let know the point to fix this?
Edited
I've checked number of bytes on encryption/decryption.
encryption output bytes : "outputStream.toByteArray().length" is 68560
decryption input bytes : "Base64.decode(encryptedString, Base64.DEFAULT).length" is also 68560
I think this means decryption process doesn't lost any part of it and encryption process may truncate part of input plain text.
is this because of restriction of AES algorithm?
Solved
As #zaph commented, not all of the bytes is written to steam.
Replacing
cipherOutputStream.write(plainString.getBytes("UTF-8"));
cipherOutputStream.close();
with
// NOTE: workaround. too bit bytes doesn't writted correctly.
byte[] bytes = plainString.getBytes("UTF-8");
int oneBulkSize = 1024;// temp value for proof of concept. might be bigger one.
int numOfBulk = (bytes.length / oneBulkSize);
for (int i = 0; i < numOfBulk; i++) {
cipherOutputStream.write(bytes, oneBulkSize * i, oneBulkSize);
}
if ((bytes.length % oneBulkSize) != 0) {
cipherOutputStream.write(bytes, oneBulkSize * numOfBulk, bytes.length % oneBulkSize);
}
cipherOutputStream.close();
solved the issue.
AES does not have a data length limitation. The encrypted data should be the same length as the input plus up to an additional 16-bytes for padding.
Add some debugging code in the encryption loop and verify that the entire input is being read. Perhaps the encryption stream needs a loop as does the decryption stream.
Another possibility is that the 183244 is the byte size with UTF-16 encoding but you are reading as UTF-8, most of the UTF-16 characters can be as one UTF-8 byte.
The following is the actual solution code by the OP #nsht, included here for completeness (from a deleted answer by #nsht):
Replacing
cipherOutputStream.write(plainString.getBytes("UTF-8"));
cipherOutputStream.close();
with
// NOTE: workaround. too bit bytes doesn't writted correctly.
byte[] bytes = plainString.getBytes("UTF-8");
int oneBulkSize = 1024;// temp value for proof of concept. might be bigger one.
int numOfBulk = (bytes.length / oneBulkSize);
for (int i = 0; i < numOfBulk; i++) {
cipherOutputStream.write(bytes, oneBulkSize * i, oneBulkSize);
}
if ((bytes.length % oneBulkSize) != 0) {
cipherOutputStream.write(bytes, oneBulkSize * numOfBulk, bytes.length % oneBulkSize);
}
cipherOutputStream.close();
solved the issue.
Related
I tried to use encryption and decryption when sending files using socket. I use the AES/CBC/PKCS5Padding algorithm and first write the IV and then the file to the stream.
The problem is that the loop does not end when the file is receiving.
I think this is due to the file size and the encrypted file seems to be smaller than the original file, While I give the size of the original file to the receiver. If this hypothesis is correct, is there a way to calculate the size of the encrypted file?
File sender
SecretKey keySpec = new SecretKeySpec(key, "AES");
byte[] iv = AES.randomNonce(16);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
outputStream = socket.getOutputStream();
outputStream.write(iv);
outputStream.flush();
inputStream = context.getContentResolver().openInputStream(message.getUri());
cipherOutputStream = new CipherOutputStream(outputStream, cipher);
long size = message.getSize();
long written = 0;
byte[] buffer = new byte[8192];
int count;
int percent = 0;
while (!isStopped && (count = inputStream.read(buffer)) > 0) {
cipherOutputStream.write(buffer, 0, count);
written += count;
int p = (int) (((float) written / (float) size) * 100);
if (percent != p) {
percent = p;
if (onProgressListener != null) {
onProgressListener.onProgress(percent);
}
}
}
cipherOutputStream.flush();
if (onProgressListener != null) {
onProgressListener.onEnd(null);
}
File receiver
inputStream = socket.getInputStream();
fileOutputStream = new FileOutputStream(file);
byte[] iv = new byte[16];
inputStream.read(iv);
SecretKey keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
cipherInputStream = new CipherInputStream(inputStream, cipher);
long size = message.getSize();
long read = size;
byte[] buffer = new byte[8192];
int count;
int percent = 0;
while (!isStopped && read > 0 && (count = cipherInputStream.read(buffer, 0, (int) Math.min(buffer.length, read))) != -1) {
fileOutputStream.write(buffer, 0, count);
read -= count;
int p = (int) (((float) read / (float) size) * 100);
if (percent != p) {
percent = p;
if (onProgressListener != null) {
onProgressListener.onProgress(100 - percent);
}
}
}
if (onProgressListener != null) {
onProgressListener.onEnd(Uri.fromFile(file));
}
The improper code
As commented by PresidentJamesK.Polk you need to close the CipherOutputStream in the sender side by calling the .close() function that calls the doFinal() to finalize the encryption.
The padding size
Although Java says PKCS5Padding actually it is PKCS#7 Padding. PKCS#5 Padding is defined for 8 octets, i.e. block ciphers with 64-bit block size like DES.
PKCS#7 standard is on the rfc2315 (10.3 note 2):
For such algorithms, the method shall be to pad the input at the trailing end with k - (l mod k) octets all having value k - (l mod k), where l is the length of the input.
An octet is one byte and the k is the block size in bytes, and it is 16 for AES.
We need to calculate IV_length + message_size_with_padding
If we assume that you have l bytes to encrypt then the output size is
16 + l + 16 - (l mod 16) therefore at most 16 byte extended due to padding.
A sample Java code
/**
* A simple function to calculate the plaintext size after
* PKCS#7 padding is applied to the message.
*
* In Java PKCS#7 = PKCS#5
*
* #param messageSize : the byte size of the plaintext
* #return message size after the PKCS#7 padding is applied
*/
public int paddedMessageSize( int messageSize ) {
// first 16 is the prepended IV
return ( 16 + messageSize + 16 - (messageSize % 16));
}
Notes
CBC mode is vulnerable to padding oracle attacks where applicable. CBC mode provides only confidentiality. If you need integrity and authentication ( you should) either use CBC with HMAC or better use authenticated encryption modes like AES-GCM which doesn't require padding at all, however, you need to store the tag, too. If you fear the nonce reuse issue for AES-GCM then you can use AES-GCM-SIV, which is a nonce misuse resistant scheme. If you are not obligated to use AES you can prefer ChaCha20-Poly1305 which might be easier to use than AES-GCM [1][2].
This is an issue of an Android project. When I run this code in Android Studio, Logcat did not show me any exception. But when I run this code in Windows using Eclipse, I got this exception:
java.io.IOException: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
So, based on Kelalaka's answer, I realized that the problem is that cipherInputStream and cipherOutputStream did not close properly. In fact, I first close inputStream and outputStream, and then cipher streams. So I closed the cipher streams first and then the other streams and now the encryption is working properly.
Check for this exception.
java.io.IOException: javax.crypto.BadPaddingException
Use case 1 (working baseline):
Use case one is straightforward and is implemented / working.
In Java, Write a stream to disk in a single fell swoop.
Wrap output stream with symmetric cipher so that contents on disk are encrypted.
Later, read from disk. Wrap input stream with same symmetric cipher
in a single fell swoop so that contents retrieved from input stream
are plaintext and match original.
Use case 2 (no suitable solution determined):
In Java, Write a stream to disk.
Allow for subsequent bytes ("chunks") to be appended to file.
Wrap output stream with symmetric cipher so that contents on disk are encrypted.
Use same cipher so that all chunks are encrypted in the same manner.
Later, read from disk. Wrap input stream with same symmetric cipher
in a single fell swoop so that contents retrieved from input stream
are plaintext and match original.
Problem statement:
Encrypting and decrypting "abc" does not yield the same result as encrypting and decrypting "a", "b", and "c" separately, and therefore the "chunked" file described in use case 2 will no be successfully decrypted.
// e.g.
decrypt(encrypt("abc")) != decrypt(encrypt("a") + encrypt("b") + encrypt("c"))
The Actual Question:
... so the question is, how might one configure a Java cipher stream that can encrypt one chunk at a time, (a) without having prior knowledge of encrypted chunks, and (b) be decipherable using a single input stream cipher wrapper (without requiring knowledge of indexes where file was appended)...
Unfortunately, in this case you can't have your cake and eat it too.
You must either
write some length bytes at the start of each chunk, or
use an encryption algorithm where decrypt(encrypt("abc")) == decrypt(encrypt("a") + encrypt("b") + encrypt("c")) (aka trivial, and not recommended)
Number 1 is definitely a better choice, and is easier than you might think. Details below.
Number 2, you could use something like a Vigenere cipher, which would allow you to decrypt the whole file in one fell swoop, but would be a compromise in terms of encryption strength.
Details on number 1
The way you would do this is by reserving, for instance, four bytes (a 32-bit integer) at the beginning of each chunk. This integer represents the length of the chunk. To decrypt you would therefore:
Read the first four bytes and convert to integer n.
Read the next n bytes and decrypt.
Read the next four bytes and convert to integer n.
Read the next n bytes, decrypt and append to the first decrypted chunk.
Repeat steps 3 and 4 until end of file is reached.
And obviously this makes the chunk encryption easy because all you have to do is first write how many encrypted bytes you're about to append.
I found a solution close enough to my particular problem (stealing from this post), albeit slightly different from the problem statement (not a single stream).
public static void appendAES(File file, byte[] data, byte[] key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
RandomAccessFile rfile = new RandomAccessFile(file,"rw");
byte[] iv = new byte[16];
byte[] lastBlock = null;
if (rfile.length() % 16L != 0L) {
throw new IllegalArgumentException("Invalid file length (not a multiple of block size)");
} else if (rfile.length() == 16) {
throw new IllegalArgumentException("Invalid file length (need 2 blocks for iv and data)");
} else if (rfile.length() == 0L) {
// new file: start by appending an IV
new SecureRandom().nextBytes(iv);
rfile.write(iv);
// we have our iv, and there's no prior data to reencrypt
} else {
// file length is at least 2 blocks
rfile.seek(rfile.length()-32); // second to last block
rfile.read(iv); // get iv
byte[] lastBlockEnc = new byte[16];
// last block
// it's padded, so we'll decrypt it and
// save it for the beginning of our data
rfile.read(lastBlockEnc);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
lastBlock = cipher.doFinal(lastBlockEnc);
rfile.seek(rfile.length()-16);
// position ourselves to overwrite the last block
}
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
byte[] out;
if (lastBlock != null) { // lastBlock is null if we're starting a new file
out = cipher.update(lastBlock);
if (out != null) rfile.write(out);
}
out = cipher.doFinal(data);
rfile.write(out);
rfile.close();
}
public static void decryptAES(File file, OutputStream out, byte[] key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
// nothing special here, decrypt as usual
FileInputStream fin = new FileInputStream(file);
byte[] iv = new byte[16];
if (fin.read(iv) < 16) {
throw new IllegalArgumentException("Invalid file length (needs a full block for iv)");
};
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key,"AES"), new IvParameterSpec(iv));
byte[] buff = new byte[1<<13]; //8kiB
while (true) {
int count = fin.read(buff);
if (count == buff.length) {
out.write(cipher.update(buff));
} else {
out.write(cipher.doFinal(buff,0,count));
break;
}
}
fin.close();
}
public static void main(String[] args) throws Exception {
// prep the new encrypted output file reference
File encryptedFileSpec = File.createTempFile("chunked_aes_encrypted.", ".test");
// prep the new decrypted output file reference
File decryptedFileSpec = File.createTempFile("chunked_aes_decrypted.", ".test");
// generate a key spec
byte[] keySpec = new byte[]{0,12,2,8,4,5,6,7, 8, 9, 10, 11, 12, 13, 14, 15};
// for debug/test purposes only, keep track of what's written
StringBuilder plainTextLog = new StringBuilder();
// perform chunked output
for (int i = 0; i<1000; i++) {
// generate random text of variable length
StringBuilder text = new StringBuilder();
Random rand = new Random();
int n = rand.nextInt(5) + 1;
for (int j = 0; j < n; j++) {
text.append(UUID.randomUUID().toString()); // append random string
}
// record it for later comparison
plainTextLog.append(text.toString());
// write it out
byte[] b = text.toString().getBytes("UTF-8");
appendAES(encryptedFileSpec, b, keySpec);
}
System.out.println("Encrypted " + encryptedFileSpec.getAbsolutePath());
// decrypt
decryptAES(encryptedFileSpec, new FileOutputStream(decryptedFileSpec), keySpec);
System.out.println("Decrypted " + decryptedFileSpec.getAbsolutePath());
// compare expected output to actual
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] expectedDigest = md.digest(plainTextLog.toString().getBytes("UTF-8"));
byte[] expectedBytesEncoded = Base64.getEncoder().encode(expectedDigest);
System.out.println("Expected decrypted content: " + new String(expectedBytesEncoded));
byte[] actualBytes = Files.readAllBytes(Paths.get(decryptedFileSpec.toURI()));
byte[] actualDigest = md.digest(actualBytes);
byte[] actualBytesEncoded = Base64.getEncoder().encode(actualDigest);
System.out.println("> Actual decrypted content: " + new String(actualBytesEncoded));
}
I'm trying to encrypt a message in Java and decrypt it in Python. Unfortunately i'm just starting with python and am not able to get the decryption working.
That's my Java Code:
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = sr.generateSeed(16);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKey aesKey = keygen.generateKey();
//save byte array in text file to recreate key later
byte[] encodedKey = aesKey.getEncoded();
new File("myPath\\AESKey.txt");
FileOutputStream fos = new FileOutputStream("myPath\\AESKey.txt");
//save AesKey in first 16 bytes and Initial Vector in next 16 bytes
fos.write(encodedKey);
fos.write(iv);
fos.close();
String secretText = "Hello cryptography";
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivSpec);
byte[] encrypted = cipher.doFinal(secretText.getBytes());
BASE64Encoder myEncoder = new BASE64Encoder();
String encodedSecretText = myEncoder.encode(encrypted);
new File("myPath\\encodedSecretText.txt");
FileOutputStream fos2 = new FileOutputStream("myPath\\encodedSecretText.txt");
fos2.write(encodedSecretText.getBytes());
fos2.close();
I was able to decrypt the message with java, but not with python. I hope someone can show me how to do this.i copied the part with padding from another answer and assume that's the problem.
I get the message: ord() expected string of length 1, but int found.
Python:
from Crypto import Random
from Crypto.Cipher import AES
import base64
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
#read bytes of aesKey
file = open("myPath/AESKey.txt","rb")
aesKey = file.read(16)
iv = file.read(16)
file.close()
sec = open("myPath/encodedSecretText.txt")
for line in sec:
encodedSecretText = line.rstrip()
sec.close()
class AESCipher:
def __init__( self, key ):
self.key = key
def encrypt( self, raw ):
raw = pad(raw)
iv = Random.new().read( AES.block_size )
cipher = AES.new( self.key, AES.MODE_CBC, iv )
return base64.b64encode( iv + cipher.encrypt( raw ) )
def decrypt( self, enc ):
enc = base64.b64decode(enc)
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc[16:] ))
aes = AESCipher(aesKey)
print(aes.decrypt(encodedSecretText))
Thanks for any hint.
You're calling ord on an integer. Which is obviously illegal. The whole point of ord is that you give it a Unicode character, as a string, and it gives you back the numerical value of the code point.
So, why do you have a number? Well, I'm not sure what you expected to have, but let's look at what you actually have. If s[-1] is an integer, then s is some kind of sequence of integers. And s is the result of calling cipher.decrypt(). As the documentation for that function says, it returns a byte string. This isn't a specific type, just a description of a type—you can find out what the actual return value is with some basic debugging, maybe like this:
cipher = AES.new(self.key, AES.MODE_CBC, iv )
plaintext = cipher.decrypt(enc[16:])
print(type(plaintext), repr(plaintext))
return unpad(plaintext)
But I'm going to guess that it's a bytes object, which (quoting from http://docs.python.org/3/library/functions.html#bytes) …
is an immutable sequence of integers in the range 0 <= x < 256.
So, s[-1] is an integer in the range [0, 256). Hence the error.
So, what should you be doing instead? Well, why are you trying to call ord? You have a byte. Presumably what you want is a byte. So… just don't call anything there.
Meanwhile, there's at least one other serious error in your code:
for line in sec:
encodedSecretText = line.rstrip()
sec.close()
As pasted, this will raise an IndentationError. And if you indent both the second and third lines, you'll get an error for reading from a closed file. So presumably you want to indent just the second one. In which case, what you're doing is going through all of the lines, stripping the trailing whitespace off each, and then doing nothing with them. At the end of the loop, encodedSecretText holds the last line of encoded text, and all of the other lines are long forgotten and unrecoverable.
If you want to read all of the text into a list of lines, you will want something like this:
encodedSecretText = []
for line in sec:
encodedSecretText.append(line.rstrip())
Or, if you want to read it into one big string, with the newlines removed, you could do the above and then encodedSecretText = b''.join(encodedSecretText), or just do skip the whole loop and do encodedSecretText = sec.read().replace(b'\n', b'').
So I have been working with the Bouncycastle libraries in an attempt to connect with a remote server. This process has been problematic from the get go and now I'm close to getting everything working but some odd things are happening.
When I first started building out the encryption process I was told to use AES 256 with PKCS7Padding. After some nagging I was provided with a c++ example of the server code. It turned out that the IV is 256 bit so I had to use the RijndaelEngine instead. Also in order for this to work correctly I have to use ZeroBytePadding.
Here is my code:
socket = new Socket(remoteIP, port);
outputStream = new PrintWriter(socket.getOutputStream());
inputStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
byte[] base_64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes("UTF-8");
Security.addProvider(new BouncyCastleProvider());
public String AESEncrypt(String out) throws IOException, DataLengthException, IllegalStateException, InvalidCipherTextException {
byte[] EncKey = key;
byte randKey;
Random randNumber = new Random();
randKey = base_64[randNumber.nextInt(base_64.length)];
EncKey[randKey&0x1f] = randKey;
RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndaelEngine), new ZeroBytePadding());
ParametersWithIV keyParameter = new ParametersWithIV(new KeyParameter(EncKey), iv);
cipher.init(true, keyParameter);
byte[] txt = out.getBytes();
byte[] encoded = new byte[cipher.getOutputSize(txt.length)];
int len = cipher.processBytes(txt, 0, txt.length, encoded, 0);
cipher.doFinal(encoded, len);
char keyChar = (char) randKey;
String encString = new String(Base64.encode(encoded));
encString = encString.substring(0, encString.length()-1) + randKey;
return encString;
}
public void AESDecrypt(String in) throws DataLengthException, IllegalStateException, IOException, InvalidCipherTextException {
byte[] decKey = key;
byte[] msg = in.getBytes();
byte randKey = msg[msg.length-1];
decKey[randKey&0x1f] = randKey;
byte[] trimMsg = new byte[msg.length-1];
System.arraycopy(msg, 0, trimMsg, 0, trimMsg.length);
in = new String(trimMsg);
RijndaelEngine rijndaelEngine = new RijndaelEngine(256);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndaelEngine), new ZeroBytePadding());
ParametersWithIV keyParameter = new ParametersWithIV(new KeyParameter(decKey), iv);
cipher.init(false, keyParameter);
byte[] encoded = Base64.decode(in.trim());
byte[] decoded = new byte[cipher.getOutputSize(encoded.length)];
int len = cipher.processBytes(encoded, 0, encoded.length, decoded, 0);
cipher.doFinal(decoded, len);
String decString = new String(decoded);
}
Here is a test function I am using to send and receive messages:
public void serverTest() throws DataLengthException, IllegalStateException, InvalidCipherTextException, IOException {
//out = AESEncrypt(out);
outputStream.write(out + "\n");
outputStream.flush();
String msg = "";
while ((msg = inputStream.readLine()) != null) {
AESDecrypt(msg);
}
}
The key and iv don't change with the exception of the last byte in the key. If I am encrypting I get a random base64 char and change the last byte to that. If its decryption I get the last byte from the message and set the last value of the key to it for decryption.
In the c++ example there was an unencrypted message and two encrypted messages. I could deal with those fine.
Here is the problem, when I send my message to the remote server "encrypted" the app waits for a response until the connection times out but never gets one. If I send the message unencrypted I get either 7 responses which I can successfully decrypt and finally
org.bouncycastle.util.encoders.DecoderException: unable to decode base64 string:
String index out of range: -4 at org.bouncycastle.util.encoders.Base64.decode(Unknown Source)
or my last line before the error will look like this:
?"??n?i???el????s???!_S=??ah????CR??l6??]?{?l??Y?????Gn???+?????9!'??gU&4>??{X????G?.$c=??0?5??GP???_Q5????8??Z\?~???<Kr?????[2\ ???a$?C??z%?W???{?.?????eR?j????~?B"$??"z??W;???<?Yu??Y*???Z?K?e!?????f?;O(?Zw0B??g<???????????,)?L>???A"?????<?????W??#\???f%??j ?EhY/?? ?5R?34r???#?1??I??????M
If I set the encryption/decryption to use PKCS7Padding I get no response when my message is encrypted still but with decryption from the server I get between 2 to 6 responses and then
org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
I am at a loss with this. I don't know what I might be doing wrong so I have come here. I'm hoping the so community can point out my errors and guide me in the right direction.
I have a bit of an update I found my error in the encryption. I wasn't placing the random base64 value at the end of the encrypted string correctly so now I am doing like this.
encString += (char)randKey;
I can get response from the server now. Now the problem is I will some times get one or two readable lines but the rest are all garbage. I asked the individuals who run the server about it and they said in some c# code that they reference the have
return UTF8Encoding.UTF8.GetString(resultArray);
and thats all I have to go off of. I have tried UTF-8 encoding any place where I do getBytes or new String, and I have tried making the BurrferReader stream UTF-8 but it's still garbage.
Have you seedn the BCgit? this has bouncycastle code and examples. I am using the Csharp version in this repository. https://github.com/bcgit/bc-java
All crypto primitive examples are stored here: https://github.com/bcgit/bc-java/tree/master/core/src/test/java/org/bouncycastle/crypto/test
Try this code for testing Aes-CBC
private void testNullCBC()
throws InvalidCipherTextException
{
BufferedBlockCipher b = new BufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
KeyParameter kp = new KeyParameter(Hex.decode("5F060D3716B345C253F6749ABAC10917"));
b.init(true, new ParametersWithIV(kp, new byte[16]));
byte[] out = new byte[b.getOutputSize(tData.length)];
int len = b.processBytes(tData, 0, tData.length, out, 0);
len += b.doFinal(out, len);
if (!areEqual(outCBC1, out))
{
fail("no match on first nullCBC check");
}
b.init(true, new ParametersWithIV(null, Hex.decode("000102030405060708090a0b0c0d0e0f")));
len = b.processBytes(tData, 0, tData.length, out, 0);
len += b.doFinal(out, len);
if (!areEqual(outCBC2, out))
{
fail("no match on second nullCBC check");
}
}
Just wondering why same plain text encrypted in Java and C doesn't generate same cipher text.
For DES algo I noticed
input text = "text", key = "test"
C cipher text len = 24
Java generates cipher text len = 8
For 256Bit AES I noticed similar difference
C cipher text len = 32
Java generates cipher text len = 16
here is my "C" code
char* enc(const char* text, const char* keyStr)
{
EVP_CIPHER_CTX ctx;
unsigned char key[32] = {0};
unsigned char iv[16] = {0};
unsigned char in[16] = {0};
unsigned char out[32]; /* at least one block longer than in[] */
memset(out, 0, 32);
int outlen1, outlen2;
memcpy(in, text, strlen(text));
memcpy(key, keyStr, strlen(keyStr));
EVP_EncryptInit(&ctx, EVP_aes_256_cbc(), key, iv);
EVP_EncryptUpdate(&ctx, out, &outlen1, in, sizeof(in));
EVP_EncryptFinal(&ctx, out + outlen1, &outlen2);
char* ret = (char*)malloc(outlen1 + outlen2+1);
memset(ret, 0, outlen1 + outlen2+1);
memcpy(ret, out, outlen1 + outlen2);
EVP_CIPHER_CTX_cleanup(&ctx);
return ret;
}
Here is the "Java" code
public static byte[] enc(byte[] input, byte[] keyStr){
byte[] output = null;
try {
byte[] newKey = getByteArrays(keyStr, 0, 32);
SecretKey secretKey = new SecretKeySpec(newKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
String ivStr = "";
byte[] ivKey = getByteArrays(ivStr.getBytes("UTF-8"), 0, 16);
IvParameterSpec ips = new IvParameterSpec(ivKey);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ips, null);
output = cipher.doFinal(input);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return output;
}
I have list of entries encrypted using C , I want to decrypt those entries in Java.
UPDATED
following update helped to get same length , both in 'C' and 'JAVA'
EVP_EncryptUpdate(&ctx, out, &outlen1, in, strlen(text));
No Just wondering why ciphertext returned by "C" and "JAVA" are different for same text and key, I'm expecting them tobe same
The reason the C code outputs 32 bytes of ciphertext is because you are padding your input string with zeros before passing it to the encryption function.
In this line:
EVP_EncryptUpdate(&ctx, out, &outlen1, in, sizeof(in));
the function has no idea that you padded your data with zeros. Since sizeof(in) is 16, it takes 16 as the length of your plaintext. And with PKCS5 padding (and AES), 16 bytes will pad to 32 bytes.
In your Java code you didn't manually pad your input, you just passed it straight to the encryption function. So the encryption function sees your plaintext size as 4 bytes, which pads to 16 with PKCS5 padding.
So your solution is simple: don't manually pad your input in the C code. Just pass the plaintext directly to EVP_EncryptUpdate. It's already designed to handle unpadded plaintext.