Trying to use PyCryptodome AES ECB decryption with offsets - java

As a bit of context to this I am converting a java file to python and am on the last operation. I'm at about 200 LOC so it makes it that much more edge of the seat...
Anyways, in java the operation is:
Cipher cipher = Cipher.getInstance("AES/ecb/nopadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keys[i], "AES"));
//doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
cipher.doFinal(save, saveOffset, 16, save, saveOffset);
In python I have this:
from Crypto.Cipher import AES
cipher = AES.new(bytes(keys[i]), AES.MODE_ECB)
cipher.decrypt(?????)
This is taken from the package under decrypt():
:Parameters:
ciphertext : bytes/bytearray/memoryview
The piece of data to decrypt.
The length must be multiple of the cipher block length.
:Keywords:
output : bytearray/memoryview
The location where the plaintext must be written to.
If ``None``, the plaintext is returned.
:Return:
If ``output`` is ``None``, the plaintext is returned as ``bytes``.
Otherwise, ``None``.
As you can see .decrypt() doesn't really have an input for offsets, but I was wondering if there was some way around this?
This is why I decided to post on SO, would I be able to send:
temp_bytes = save[offset]
temp_decrypt = cipher.decrypt(temp_bytes)
save[offset] = temp_decrypt
Or when decrypting does it use the whole file as context and i will get the wrong output? I would love to just do it and test it but the output is just gibberish that i will have to write another program to parse (another java to python project).

What ended up working was:
save[saveOffset:saveOffset+16] = cipher.decrypt(save[saveOffset:saveOffset+16])
Did not expect it to work so easily

Related

AES decrypt in python when key and IV is Java array of bytes

I have the following 2 values :
AES key it is a Java array of bytes
64,67,-65,88,-19,-118,-16,-53,-81,-98,44,-83,82,-90,124,112,-120,42,92,67,104,23,44,8,2,-96,-106,-28,57,99,76,73
IV also a Java array of bytes
50,52,3,36,-90,81,109,-56,24,122,-3,27,-96,56,118,-60
The encryption mode is : AES/CBC/PKCS5PADDING
Now I need to use these 2 values to decrypt my cipher text using python. I am trying to use pycrypto but am open on exploring other options as well.
Now because Java's array of bytes is signed and (it looks like) python (2.7) does not recognize signed array of bytes, I am having trouble doing this decryption.
I did find a bunch of SO discussions around similar topics, but most of them were generating the IV and the key, and not really using an already existing one, for eg.
Encrypt & Decrypt using PyCrypto AES 256
Using a byte array as key for AES algorithm in Python (looked promising but did not help much)
Python PyCrypto encrypt/decrypt text files with AES
So the above solutions have not worked for me, unless I did something fundamentally wrong while tweaking them for my need.
Here's one of my attempts (bits and pieces taken from the SO discussions mentioned above)
from Crypto import Random
from Crypto.Cipher import AES
import base64
key =
[64,67,-65,88,-19,-118,-16,-53,-81,-98,44,-83,82,-90,124,112,-120,42,92,67,104,23,44,8,2,-96,-106,-28,57,99,76,73]
iv = [50,52,3,36,-90,81,109,-56,24,122,-3,27,-96,56,118,-60]
aes_key = ''.join(map(lambda x: chr(x % 256), key))
iv_data = ''.join(map(lambda x: chr(x % 256), iv))
def pad(s):
return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def decrypt(ciphertext, key):
ciphertext = base64.b64decode(ciphertext)
iv = ciphertext[:AES.block_size]
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext[AES.block_size:])
return plaintext.rstrip(b"\0")
And then calling decrypt() as :
decrypt('HaCiIld192v2QE3/zcuAxhpr39HRXvsHQYSJE/VT9FuWWK7envVDRaaDR6gpmh0zmw09/440O7UAtVb5qBLddZH37TASWUMXAN0Idy7B0VtpgPgMHOLGJG6axWCHgieaTleFDaRKP+z7WN+4fj5RGw==', aes_key)
gives the output as :
Out[9]:
'\xdd\x99\xab\x8bI\xcb\xf0?\xcd\x1c\x1eb\x109\xe8c\xc0\x105\xa6\xdb\xe4\xdb\x08\xc7\xdbHT\x08.\x10\x9e\xe1g\xfe\xd1\xc2\x92\xdbH\x8e\x9bV\xae^\r:\x98%\x8d\x06\xdf\xaf\t\xb5m\xfb\xf3Q+s"CK\xf4\xc2\xe7\xd2\xfd\xfdq\xcc)\xcf\x99&\xffj\xc4\xc1\x1a$ML\xe9\xe7(\xef\x1c\x81vx\xf56'
I tried :
a = b'\xdd\x99\xab\x8bI\xcb\xf0?\xcd`\x1c\x1eb\x109\xe8c\xc0\x105\xa6\xdb\xe4\xdb\x08\xc7\xdbHT\x08.\x10\x9e\xe1g\xfe\xd1\xc2\x92\xdbH\x8e\x9bV\xae^\r:\x98%\x8d\x06\xdf\xaf\t\xb5m\xfb\xf3Q+`s"CK\xf4\xc2\xe7\xd2\xfd\xfdq\xcc)\xcf\x99&\xffj\xc4\xc1\x1a$ML\xe9\xe7(\xef\x1c\x81vx\xf56'
a.encode("hex")
and output is :
'dd99ab8b49cbf03fcd601c1e621039e863c01035a6dbe4db08c7db4854082e109ee167fed1c292db488e9b56ae5e0d3a98258d06dfaf09b56dfbf3512b607322434bf4c2e7d2fdfd71cc29cf9926ff6ac4c11a244d4ce9e728ef1c817678f536'
which is definitely not the plain text I was expecting.
So I need help figuring this out, as to how can I decrypt (using Python 2.7) an AES encrypted cipher text given the key and IV as Java array of bytes?
As per suggestions in the comments below (#MaartenBodewes thanks) I tried this :
So, I checked the base64 of the bytearray, key, in java as :
byte arr[] = new byte[] {64,67,-65,88,-19,-118,-16,-53,-81,-98,44,-83,82,-90,124,112,-120,42,92,67,104,23,44,8,2,-96,-106,-28,57,99,76,73};
String base64 = new String(Base64.encodeBase64(arr));
and then to achieve the same in python did some changes to python code above to do the same as : (I realized that java treats a byte array as an array of signed 2s complement data, from an excellent discussion at Java byte array contains negative numbers, and so had to accommodate for that in python)
key = [64,67,-65,88,-19,-118,-16,-53,-81,-98,44,-83,82,-90,124,112,-120,42,92,67,104,23,44,8,2,-96,-106,-28,57,99,76,73]
b64encode(bytearray([(lambda x:int((bin(x & 0xff)),2))(x) for x in key]))
and both these resulted in the same output as :
QEO/WO2K8MuvniytUqZ8cIgqXENoFywIAqCW5DljTEk=
And I thought that not that I am sure that both these keys (Java and python) are the same, the decryption should work. And hence I tried once again:
decrypt('HaCiIld192v2QE3/zcuAxhpr39HRXvsHQYSJE/VT9FuWWK7envVDRaaDR6gpmh0zmw09/440O7UAtVb5qBLddZH37TASWUMXAN0Idy7B0VtpgPgMHOLGJG6axWCHgieaTleFDaRKP+z7WN+4fj5RGw==', str(aes_key))
And I got the exact same output as earlier:
'\xdd\x99\xab\x8bI\xcb\xf0?\xcd\x1c\x1eb\x109\xe8c\xc0\x105\xa6\xdb\xe4\xdb\x08\xc7\xdbHT\x08.\x10\x9e\xe1g\xfe\xd1\xc2\x92\xdbH\x8e\x9bV\xae^\r:\x98%\x8d\x06\xdf\xaf\t\xb5m\xfb\xf3Q+s"CK\xf4\xc2\xe7\xd2\xfd\xfdq\xcc)\xcf\x99&\xffj\xc4\xc1\x1a$ML\xe9\xe7(\xef\x1c\x81vx\xf56'

Why can I still decrypt parts of my data after flipping a bit in the ciphertext?

I am using in my application AES algorithm to encrypt my data. My key is of 256 bit. The encrypted token formed is of this sort:
pRplOI4vTs41FICGeQ5mlWUoq5F3bcviHcTZ2hN
Now if I change one bit of the token alphabet from upper case to lower case say some thing like this:
prplOI4vTs41FICGeQ5mlWUoq5F3bcviHcTZ2hN
Some part of the token is getting decrypted along with junk value. My concern is why even some part of the data is getting visible when as such one bit is changed.My code for encryption is as follows:
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Key secretKeySpecification = secretKeyData.getKey();
cipher.init(
Cipher.ENCRYPT_MODE,
secretKeySpecification,
new IvParameterSpec(secretKeyData.getIV().getBytes("UTF-8")));
byte[] bytesdata = cipher.doFinal(data.getBytes());
String encodedData = new BASE64Encoder().encode(bytesdata)
My code for decryption is:
Key secretKeySpecification = decryptionKeyDetails.getKey();
cipher.init(Cipher.DECRYPT_MODE, secretKeySpecification,
new IvParameterSpec(decryptionKeyDetails.getIV()
.getBytes("UTF-8")));
byte[] bytesdata;
byte[] tempStr = new BASE64Decoder()
.decodeBuffer(splitedData[0]);
bytesdata = cipher.doFinal(tempStr);
return new String(bytesdata);
Ciphertext modes of operation have specific forms of error propagation. There is such as thing as Bi-IGE (Bi-directional Infinite Garble Extension, that does change the whole plaintext if any error is introduced. However, it requires more than one pass, and it still won't protect you from getting random data if a bit was changed.
In the end, listen to Oleg and Codes (and Wikipedia and even me) and add an authentication tag to your ciphertext. Validate the authentication tag (e.g. HMAC) before decryption. Don't forget to include other data in your protocol such as the IV, or you may have a plaintext for which the first block has been changed.

Encrypt in java, decrypt in node.js

I need to encrypt in java and decrypt with node.js. The decryption result is corrupted.
Here is the java code:
public String encrypt(SecretKey key, String message){
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] stringBytes = message.getBytes("UTF8");
byte[] raw = cipher.doFinal(stringBytes);
// converts to base64 for easier display.
BASE64Encoder encoder = new BASE64Encoder();
String base64 = encoder.encode(raw);
return base64;
}
Here is the node.js code:
AEse3SCrypt.decrypt = function(cryptkey, encryptdata) {
encryptdata = new Buffer(encryptdata, 'base64').toString('binary');
var decipher = crypto.createDecipher('aes-128-cbc', cryptkey);
decipher.setAutoPadding(false);
var decoded = decipher.update(encryptdata);
decoded += decipher.final();
return decoded;
}
As a key I use: "[B#4ec6948c"
The jave encrypted result is: "dfGiiHZi8wYBnDetNhneBw=="<br>
The node.js result is garbich....
In java I use "PKCS5Padding". What should be done in node.js regarding padding? I made setAutoPadding(false). If I don't do it I get error decipher fail. (only from node.js version 0.8).
I tried to remove utf8 encoding from the java in order to be complementary with the node.js but it didn't work.
Any idea what is wrong?
As a key I use: "[B#4ec6948c"
That sounds very much like you're just calling toString() on a byte array. That's not giving you the data within the byte array - it's just the default implementation of Object.toString(), called on a byte array.
Try using Arrays.toString(key) to print out the key.
If you were using that broken key value in the node.js code, it's no wonder it's giving you garbage back.
I tried to remove utf8 encoding from the java in order to be complementary with the node.js
That's absolutely the wrong approach. Instead, you should work out how to make the node.js code interpret the plaintext data as UTF-8 encoded text. Fundamentally, strings are character data and encryption acts on binary data - you need a way to bridge the gap, and UTF-8 is an entirely reasonable way of doing so. The initial result of the decryption in node.js should be binary data, which you then "decode" to text via UTF-8.
I'm afraid I don't know enough about the padding side to comment on that.

Problems encoding/decoding using AES-128-CBC

So basically I have these snippets of code and would like them to produce the same output:
require 'openssl'
aes = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
aes.key = "aaaaaaaaaaaaaaaa"
aes.iv = "aaaaaaaaaaaaaaaa"
aes.encrypt
encrypted = aes.update("1234567890123456") << aes.final
puts encrypted.unpack('H*').join
This prints:
8d3bbffade308f8e4e80cb77ecb8df19ee933f75438cec1315c4a491bd1b83f4
And this Java code:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
String key = "aaaaaaaaaaaaaaaa";
String textToEncryptpt = "1234567890123456";
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(key.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(textToEncryptpt.getBytes());
System.out.println(Crypto.bytesToHex(encrypted));
Prints:
2d3760f53b8b3dee722aed83224f418f9dd70e089ecfe9dc689147cfe0927ddb
Annoying thing is that it was working a couple of days ago... so I am not sure what happened. What's wrong with this code? Do you see anything unusual?
Ruby script is wrong. You have to first call the encrypt method, and then set the key and iv:
require 'openssl'
aes = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
aes.encrypt
aes.key = "aaaaaaaaaaaaaaaa"
aes.iv = "aaaaaaaaaaaaaaaa"
encrypted = aes.update("1234567890123456") << aes.final
puts encrypted.unpack('H*').join
I figured out because when trying to decode an encrypted string I got:
aescrypt.rb:13:in `final': bad decrypt (OpenSSL::Cipher::CipherError)
from aescrypt.rb:13:in `<main>'
Seems you found already the reason that your script does give different results.
Some more things to consider:
Don't ever hardcode the key in the program - that way you can't easily change it, and if someone gets access to your program code, she also gets to see the key.
Don't ever use a constant initialization vector. Instead, generate a random one and send it together with the ciphertext. Alternatively, if you generate the key from a password and some salt, you can also generate the IV from the same ... but don't use the key directly as IV.
Your key/IV values are strings, not bytes. String.getBytes() (in Java) converts the string to bytes using some encoding. The encoding used is system-dependent, and none of the usual String encodings (UTF-8, Latin-1, ...) can represent all bytes as printable (and typeable) characters. Preferably use something like Base64 or hex-encoding, if you have to store your key as string.
And whenever you transform a string to bytes, specify an encoding (and use the same encoding later for retrieving it).
#Cristian, For key and initial vector, you can create a function by using today's date plus the secure fixed keyword.
Eg: key = January 8, 2012 + Key
And for initial vector,
Eg: iv = January 8, 2012 + IV
Then enter that data(key and iv) to MD5 it will produce the output 16 bytes that you can use for the Key and IV. Every day, key and iv will change randomly.
Make sure both systems use the same date format and setup on the same date.

AES difference between iPhone (Objective-c) and Java

I have been tearing my hair out all day trying to solve this...
I have an objective-c client running on the iPhone, connecting to a Java server. The iPhone is encrypting data using AES but I cannot decrypt it on the server. I am using a known passphrase and message (single string) and am generating the byte array on the iPhone, generating a comparison byte array on the Java server using the same key and message but the byte arrays are completely different (and hence can't be decoded on the Java side).
The client is using the CommonCrypto library with the following settings...
Data is an NSData holding the word "message" using dataUsingEncoding:NSASCIIStringEncoding
Key is an NSData holding the phrase "1234567891123456" again using the encoding as above.
Algorithm is kCCAlgorithmAES128
Options is kCCOptionsPKCS7Padding (which I believe equates to ECB on the server?!)
The server is using the following code...
byte[] key = "1234567891123456".getBytes();
Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec k = new SecretKeySpec(key, "AES");
c.init(Cipher.ENCRYPT_MODE, k);
byte[] encryptedData = c.doFinal("message".getBytes());
BUT the data in encryptedData does not match that which is being generated in the objective-c code, the byte arrays are completely different.
Can anyone see anything obvious I am doing wrong? I think the settings are all the same... :(
UPDATE - As requested....
Ok so here goes....
iPhone client is encrypting the following string "message"
It uses the key "1234567891123456"
It uses an initialisation vector of "1010101010101010"
It is using AES128, with CBC mode (as far as I can tell) and options of kCCOptionsPKCS7Padding.
The result of the encryption (with base64 encoding) is UHIYllDFAXl81ZM7OZPAuA==
The server is encrypting the same string, with the same key and initialisation vector.
It is using the following Cipher.getInstance("AES/CBC/PKCS5Padding");
The result of the encryption (with base64 encoding) is ALBnFIHysLbvAxjvtNo9vQ==
Thanks.
UPDATE 2 - As requested...
Here is the iPhone code....
NSData *toencrypt = [#"message" dataUsingEncoding:NSASCIIStringEncoding];
NSData *pass = [#"1234567891123456" dataUsingEncoding:NSASCIIStringEncoding];
NSData *iv = [#"1010101010101010" dataUsingEncoding:NSASCIIStringEncoding];
CCCryptorStatus status = kCCSuccess;
NSData *encrypted = [toencrypt dataEncryptedUsingAlgorithm:kCCAlgorithmAES128 key:pass initializationVector:iv options:kCCOptionPKCS7Padding error:&status];
NSString *text = [NSString base64StringFromData:encrypted length:[encrypted length]];
The NSData category for encrypting comes from here...
http://github.com/AlanQuatermain/aqtoolkit/tree/master/CommonCrypto/
Incidentally, I have checked the byte arrays that are in toencrypt, pass and iv and they match those that are on the server.
This is not my area but it looks like on the client you have PKCS7 but on the server you have PKCS5.
I just ran across the exact same problem. I am using CommonCrypto on the iOS client, using settings:
NSData * encrypted = [data dataEncryptedUsingAlgorithm:kCCAlgorithmAES128 key:pass initializationVector:iv options:kCCOptionPKCS7Padding error:&status];
The server uses Cipher.getInstance("AES/CBC/PKCS5Padding"); with the same key and initialization vector as the client.
After banging my head against the wall for the last few hours, I finally followed Jason's advice and checked the dataEncryptedUsingAlgorithm routine and printed out keyData right after FixKeyLengths. It turns out my 128bit key was extended to 192bit with 0s added to the end. After fixing this, everything works properly. :)
Update: My answer was posted almost 2 years ago, and this issue seems to be fixed in the latest NSData+CommonCrypto code. Specifically, this was the part that caused the issue:
static void FixKeyLengths( CCAlgorithm algorithm, NSMutableData * keyData, NSMutableData * ivData )
{
NSUInteger keyLength = [keyData length];
switch ( algorithm )
{
case kCCAlgorithmAES128:
{
if ( keyLength <= 16 )
{
[keyData setLength: 16];
}
else if ( keyLength <= 24 )
{
[keyData setLength: 24];
}
else
{
[keyData setLength: 32];
}
break;
}
The first check keyLength <= 16 wasn't there before.
If you are still experiencing problems now, it's probably something else.
What mode is the iPhone using with AES? You don't list anything, so perhaps that means it's using no chaining (ECB).
However, on the Java side, you are using CBC, but not specifying an initialization vector. That is definitely wrong. If you really are using CBC, you have to have the IV that was used during encryption. The IV is not secret; it can be sent along with the ciphertext.
If you are really using ECB, there is no IV, but your Java specifies the wrong mode.
Based on your samples, the server is doing it right, and the client is not.
Looking at the data, I would guess that the key is wrong. Please show us the iPhone code, especially the code to go from "1234567891123456" to your key.
I recently ran across this in another project. The problem was that the key was one byte too long to fit into the char buffer inside of the dataEncryptedUsingAlgorithm method.
The problem was that the getBytes method on the NSString was soft-failing. It would copy most of the string into the buffer, but then since the key was one byte too long, it would "mark" the operation as failed by setting the first char to NUL (char 0).
Step into that method in Xcode and see what your key char[16] buffer looks like. It may have this same problem, and have the contents { 0, '2', '3', '4', ... }.

Categories

Resources