byte[] to String returns different String - java

For a given byte[], always the same, I'd like to get the corresponding String. the byte[] result has always the same value.
However the String returned is never the same, each time I launch my app the result changes.
byte[] results = cipher.doFinal(text.getBytes("UTF-8"));
String result = Base64.encodeBase64String(results);
I tried several other ways to get my String like String result = new String(results, "UTF-8");, with Array,... but it remains different everytime.
This is happening after a cipher encryption. Here is the full code:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5padding");
byte[] keyBuf= new byte[16];
byte[] b= key.getBytes("UTF-8");
int len= b.length;
if (len > keyBuf.length) len = keyBuf.length;
System.arraycopy(b, 0, keyBuf, 0, len);
SecretKeySpec keySpec = new SecretKeySpec(keyBuf, "AES256");
byte[] ivBuf= new byte[16];
//IvParameterSpec ivSpec = new IvParameterSpec(ivBuf);
IvParameterSpec ivSpec=null;
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] results = cipher.doFinal(text.getBytes("UTF-8"));
String result = Base64.encodeBase64String(results);
return result;
How can I ensure that the String "result" will remains the same?

You're using a different IV every time you encrypt - therefore you'll get different ciphertext each time you encrypt, too. Your results byte array is different each time, therefore the base64 representation is different.
If you really want to get the same result each time you encrypt the same input, you'll need to use the same IV each time... but be aware that that will reduce the security significantly. (Note that currently you're not even doing anything with ivSpec. You would probably want to pass it as a third argument to Cipher.init... but you'd want to do it having initialized it with an IV, not just using null.)

Related

PHP AES-256-CBC encrypted data is different from JAVA AES/CBC/PKCS5PADDING

I have a java code working perfectly
public static String encrypt(String message, String sercretKey)
{
String base64EncryptedString = "";
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digestOfPassword = md.digest(sercretKey.getBytes("utf-8"));
byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
byte[] iv = Arrays.copyOf(digestOfPassword, 16);
SecretKey key = new SecretKeySpec(keyBytes, "AES");
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, ivParameterSpec);
byte[] plainTextBytes = message.getBytes("utf-8");
byte[] buf = cipher.doFinal(plainTextBytes);
byte[] base64Bytes = Base64.getEncoder().encode(buf);
base64EncryptedString = new String(base64Bytes);
return base64EncryptedString;
}
I have tried using below code to recreate this above code in PHP
function encryptTest($sSecretKey,$sValue)
{
$key = hash('sha256', $sSecretKey,false);
$key = utf8_encode($key);
$key = substr($key, 0, 24);
$iv = substr($key, 0, 16);
$data = $sValue;
$outEnc = openssl_encrypt($data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($outEnc);
}
But showing different results. What I have missed.
(Same types of questions are available in StackOverflow, but pointing my issues)
There are the following issues:
In the PHP code, the key is currently returned hex encoded. Instead, it must be returned as bytes string. To do this, the third parameter in hash() must be switched from false to true.
In the Java code a 192 bits key is used, i.e. AES-192. Accordingly, in the PHP code "AES-192-CBC" must be applied (and not "AES-256-CBC").
The utf8_encode() call in the PHP code is to be removed, as this corrupts the key.
With these changes, both codes provide the same ciphertext.
Security:
Using SHA256 as key derivation is insecure. Instead apply a dedicated algorithm like Argon2 or PBKDF2. Also, using the key (or a part of it) as IV is insecure as it results in the reuse of key/IV pairs. Instead, a randomly generated IV should be applied for each encryption.

Using hashing with padding and blockcipher modes

I am writing a cryptographie program and want to use several cipher block- and stream modes together with hashing mechanisms. I do not have any problems with encrypting, decrypting and verifying the message with stream modes like OFB, but I have problems with decrypting and verifying the message with blockcipher moder, when they use padding.
For example I use ECB (I know it is not very good) with PKCS7Padding and SHA-256. After I decrypt the message, it has some chars at the end. Besides that I am getting the message, that the hash-digest is not equal to the original digest.
This problem does not happen, when I do not use padding.
Here is my code:
#Override
public byte[] encrypt(byte[] input) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/" + getPadding(), "BC");
cipher.init(Cipher.ENCRYPT_MODE, getKey());
byte[] output = getBytesForCipher(cipher, input);
int ctLength = cipher.update(input, 0, input.length, output, 0);
updateHash(input);
cipher.doFinal(getDigest(), 0, getDigest().length, output, ctLength);
return output;
}
protected byte[] getBytesForCipher(Cipher cipher, byte[] input) {
return new byte[cipher.getOutputSize(input.length + hash.getDigestLength())];
}
protected void updateHash(byte[] input) {
hash.update(input);
}
public byte[] decrypt(byte[] input) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/" + getPadding(), "BC");
cipher.init(Cipher.DECRYPT_MODE, getKey());
byte[] output = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, output, 0);
cipher.doFinal(output, ctLength);
return removeHash(output);
}
protected byte[] removeHash(byte[] output) {
int messageLength = output.length - hash.getDigestLength();
hash.update(output, 0, output.length - hash.getDigestLength());;
byte[] realOutput = new byte[messageLength];
System.arraycopy(output, 0, realOutput, 0, messageLength);
messageValid = isValid(output);
return realOutput;
}
private boolean isValid(byte[] output) {
int messageLength = output.length - hash.getDigestLength();
byte[] messageHash = new byte[hash.getDigestLength()];
System.arraycopy(output, messageLength, messageHash, 0, messageHash.length);
return MessageDigest.isEqual(hash.digest(), messageHash);
}
I am using the bouncycastle provider.
If you take a look at getOutputSize method of Cipher you will get the following from the documentation:
The actual output length of the next update or doFinal call may be smaller than the length returned by this method.
And this is exactly what is biting you. As the cipher instance has no way to determine the amount of padding before decrypting, it will assume that the output / plaintext size is the same size as the plaintext size. Actually, as PKCS#7 padding is always performed, it may assume one byte too much in the JCE implementation.
So you cannot just ignore the response of doFinal; you need to resize the array (using the Arrays class for instance) or grab the plaintext and hash from the right location in the buffer.
Obviously a stream cipher will not have this issue as the plaintext size and ciphertext size are identical.
Usually a keyed hash (i.e. MAC or HMAC) or authenticated cipher is used to make sure that the ciphertext is not altered. Using a hash over the plaintext may not fully protect your plaintext.

Java NIO + AES Encryption from Client to Server - ByteBuffer issue

I'm quite a newbie regarding encryption and NIO,
I have the following code for client:
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] encrypted = cipher.doFinal(msg.getBytes());
System.out.println("Encrypted info: " + encrypted);
String send = encrypted.toString();
bytebuf = ByteBuffer.allocate(48);
bytebuf.clear();
bytebuf.put(send.getBytes());
bytebuf.flip();
while(bytebuf.hasRemaining()) {
nBytes += client.write(bytebuf);
}
and the following code for server:
// Server receives data and decrypts
SocketChannel socket = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
nBytes = socket.read(buf);
String data = new String(buf.array()).trim();
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secret);
byte[] decrypted = cipher.doFinal(data.getBytes());
System.out.println("Decrypted Info: " + new String(decrypted));
When a message is sent from the Client to the Server, "HELLO" for example is encrypted to [B#34d74aa5 and on the Server side I get *Data packet found as [B#34d74aa5.
Till here everything looks fine, but I get the following exception:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
I suspect that I have some issue with the way the data is coming out of the buffer on the server side?
Any ideas on this?
UPDATE:
**Based on Erickson's answer this is the final solution
javax.crypto.BadPaddingException: Given final block not properly padded
Client Code:
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
System.out.println("Encrypted info: " + text);
bytebuf = ByteBuffer.allocate(32);
bytebuf.clear();
bytebuf.put(text.getBytes());
bytebuf.flip();
while(bytebuf.hasRemaining()) {
nBytes += client.write(bytebuf);
}
Server Code:
LOGGER.info("Confirming write");
String data = new String(buf.array());
LOGGER.info("Data packet found as {}", data);
/*******************************************************/
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
String key1 = "1234567812345678";
byte[] key2 = key1.getBytes();
SecretKeySpec secret = new SecretKeySpec(key2, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted Info: " + new String(decrypted, StandardCharsets.UTF_8));
Your cipher text, encrypted, is a byte[], and invoking toString() on an array doesn't render the array content, it returns type ([B) and hash code (#34d74aa5) information as described by Object.toString().
You can't just use new String(encrypted) either. When a byte array is decoded to text, the decoder will replace any invalid byte sequences with the replacement character, \uFFFD (�). Thus, information is lost and subsequent decryption will fail.
Use an encoding like base-64 to convert byte sequences to printable characters instead. Don't junk up your code with third-party libraries for this; you can use javax.xml.bind.DatatypeConverter.
/* Client: */
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
…
/* Server: */
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = Cipher.doFinal(encrypted);
System.out.println(new String(decrypted, StandardCharsets.UTF_8);
You should also be explicit in selecting your mode and padding (like "AES/CBC/PKCS5Padding") because there's no guarantee the recipient will use the same provider, or that the same provider will use the same defaults over time. Same goes for specifying character encodings, like UTF-8.
The AES scheme is a "block cipher" it works on fixed-size blocks of data. You are creating a "raw" Cipher instance, which will expect you to make sure that every byte array that you pass to the cipher is aligned to the cipher's "native" block length. That's usually not what you want to do.
An additional problem that you are exposing yourself to in using the cipher "raw", although it's not causing an actual error, is that if you were to pass it the same block of data on separate occasions, each time, that block would be encrypted identically, therefore giving an attacker clues as to the structure of the data. Again, that's usually not what you want to do in a practical application.
So usually, you need to specify two extra things: a padding scheme, which determines what happens when sections of data are not exactly aligned to a block size, and a block mode, which determines what scheme the cipher will use to avoid identical input blocks being encrypted to identical output blocks. The block mode generally needs initialising with a "starting state" called the initialisation vector (you could use a default state of "all zero", but that's less secure).
So you need to do two things:
You need to initialise you cipher with a padding scheme and block
mode, e.g. "AES/CBC/PKCS5PADDING"
For additional security, you would also usually set up (and transmit
before the data) a random initialisation vector. See this example for more
information.
You are converting the ciphertext, which is a byte[], to a String here:
byte[] encrypted = cipher.doFinal(msg.getBytes());
String send = encrypted.toString();
This is incorrect. You also cannot do new String(byte[]) because the byte[] is random, not a stream of character data in the platform default encoding assumed by new String(byte[]). You should convert the byte[] data to a String by using a hex or base64 encoding (I recommend Apache Commons Codec) e.g.
hexEncodedCipherText = new String(Hex.encodeHex(binaryCipherText))
On the server-side, use the opposite operation to convert the hex or base64 encoded data back to a byte[] before decryption e.g.
binaryCipherText = Hex.decodeHex(hexEncodedCipherText.toCharArray());
UPDATE:
The updated question is not working during decryption because of the incorrect use of the initialization vector. You don't specify an IV during encryption, which means Java will generate a random one. You need to obtain this random IV from the cipher by calling cipher.getIV() after the encryption (or specify it explicitly, though generating a random one is more secure). Then, during the decryption, create the IvParameterSpec using the IV created during encryption. In addition, you will need to encode/decode the IV in the same manner as the ciphertext, since it is also binary data.
UPDATE 2:
I see you have updated your question with the IV, but you are using a null IV. Generally, this is only "safe" when you have a unique key for every message you send. If your key is fixed or re-used for any significant length of time, you should generate a unique IV for each encryption/decryption. Otherwise, you are leaving yourself open to cryptanalysis based on multiple ciphertexts encrypted with the same key and IV.

Java AES Encryption (working) and Python Decryption (not working)

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'').

Decrypt AES encrypted file in java

I have a file encrypted with java application using AES. I also have a key file was encrypted with. But i can't understand how to use the key to decrypt file. Most tutorials and examples create temporary random key, encrypt file and decrypt it in one place.
So, question is how to specify a key which have to be used for decryption?
EDIT:
Samples i found use following code to generate key. I have no idea where i can use my key here.
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey key = kgen.generateKey();
Just to summarise my comments to Lucifer's answer.
If you don't know what padding was used to encrypt, then decrypt with 'no padding' set. That will decrypt everything, including the padding, and won't throw an error because of mismatched padding.
When you have decrypted the cyphertext, have a look at the last block of the output and see what padding was used. Different paddings leave different byte patterns, so it is usually easy enough to tell.
Set your decryption method to expect the correct type of padding, and it will be automatically removed for you.
The answer could be simply to put the key data as bytes into a SecretKeySpec like this:
SecretKeySpec aesKey = new SecretKeySpec(myKeyData, "AES");
Note that SecretKeySpec implements the Key interface, so you can use it directly in a Cipher.init() method. So there is no SecretKeyFactory needed, which you would use otherwise.
Please try following methods, if might helpful for you.
private static byte[] cipherData(PaddedBufferedBlockCipher cipher, byte[] data)
throws Exception
{
int minSize = cipher.getOutputSize(data.length);
byte[] outBuf = new byte[minSize];
int length1 = cipher.processBytes(data, 0, data.length, outBuf, 0);
int length2 = cipher.doFinal(outBuf, length1);
int actualLength = length1 + length2;
byte[] result = new byte[actualLength];
System.arraycopy(outBuf, 0, result, 0, result.length);
return result;
}
private static byte[] decrypt(byte[] cipher, byte[] key, byte[] iv) throws Exception
{
PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CBCBlockCipher(
new AESEngine()));
CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key), iv);
aes.init(false, ivAndKey);
return cipherData(aes, cipher);
}
private static byte[] encrypt(byte[] plain, byte[] key, byte[] iv) throws Exception
{
PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CBCBlockCipher(
new AESEngine()));
CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key), iv);
aes.init(true, ivAndKey);
return cipherData(aes, plain);
}
Complete example of encrypting/Decrypting a huge video without throwing Java OutOfMemoryException and using Java SecureRandom for Initialization Vector generation. Also depicted storing key bytes to database and then reconstructing same key from those bytes.
https://stackoverflow.com/a/18892960/185022

Categories