I use PHP to encrypt use AES/GCM to communicate with JAVA,but it does not work。 This is the code。I don't know where is the wrong?
<?php
$key = "123456789012345678901234567890";
$plaintext = "aaaaaaa";
$encryptStr = aesGcmEncrypt($plaintext, $key);
echo "加密后:" . $encryptStr;
function aesGcmEncrypt($plaintext, $key)
{
$ivlen = openssl_cipher_iv_length($cipher = "aes-128-gcm");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_NO_PADDING, $iv, $tag);
$ciphertext = base64_encode($iv . $ciphertext_raw . $tag);
return $ciphertext;
}
function decrypt($str, $key)
{
$encrypt = base64_decode($str);
$ivlen = openssl_cipher_iv_length($cipher = "aes-128-gcm");
$tag_length = 16;
$iv = substr($encrypt, 0, $ivlen);
$tag = substr($encrypt, -$tag_length);
$ciphertext = substr($encrypt, $ivlen, -$tag_length);
$ciphertext_raw = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_NO_PADDING, $iv, $tag);
return $ciphertext_raw;
}
this is the java code。
private static String aesGcmEncrypt(String content, byte[] key) {
try {
System.out.println(content);
System.out.println(content.getBytes(UTF_8).length);
// 根据指定算法ALGORITHM自成密码器
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
SecretKeySpec skey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
//获取向量
byte[] ivb = cipher.getIV();
byte[] encodedByteArray = cipher.doFinal(content.getBytes(UTF_8));
byte[] message = new byte[ivb.length + encodedByteArray.length];
System.arraycopy(ivb, 0, message, 0, ivb.length);
System.arraycopy(encodedByteArray, 0, message, ivb.length, encodedByteArray.length);
String ss = Base64.getEncoder().encodeToString(message);
return ss;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
| BadPaddingException e) {
return null;
}
}
JAVA code can't modify,because it is not mine,I must adapt to the java code.
Both codes probably behave differently than you expect.
In the Java code no padding is used at all, although PKCS5Padding (in Java a synonym for PKCS7Padding) is specified. The SunJCE Provider disables the specified PKCS5Padding for GCM and applies NoPadding. This is useful because GCM is a stream cipher mode that does not require padding.
It should be mentioned that the behavior is version dependent. Only earlier JDK versions (e.g. 8, 11, 12) accept PKCS5Padding for GCM and run it as NoPadding. Later JDK versions (e.g. 14, 15), in contrast, raise an exception. Also, other providers may behave differently.
In the PHP code the ciphertext is returned by openssl_encrypt as raw data and therefore is only Base64 encoded once (namely after concatenation with IV and tag), as it should be. Thus the code behaves as if the flag OPENSSL_RAW_DATA is set. This is because OPENSSL_NO_PADDING (with the value 3) is used, which is actually only defined for asymmetric encryption but not for symmetric encryption, and therefore should not be applied here at all. Flags for symmetric encryption are OPENSSL_RAW_DATA (with the value 1) and OPENSSL_ZERO_PADDING (with the value 2), so that OPENSSL_NO_PADDING is equivalent to OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, thus implicitly setting OPENSSL_RAW_DATA.
Note that OPENSSL_ZERO_PADDING does not enable Zero padding, but disables the default PKCS7 padding. Similar to the SunJCE Provider, also openssl implicitly disables the default PKCS7 padding for GCM, i.e. no matter if OPENSSL_ZERO_PADDING is set or not, no padding is used for GCM.
In summary it can be said that padding and flags are ruled out as the reasons for the error: In both codes there is no padding applied, in the PHP code OPENSSL_RAW_DATA is set.
Unfortunately you did not describe the error, so just guesses are possible. Probably the issue is caused by incompatible keys, since both codes work on my machine and are compatible.
As already mentioned in the comments, for AES-128/192/256 a 16/24/32 bytes key must be used. In the Java code the length of the key determines the AES variant, i.e. a length of e.g. 16 bytes implies AES-128. In the PHP code the AES variant must be explicitly specified, e.g. aes-128-gcm. A key that is too long is simply cut off, a key that is too short is padded with 0 values.
For example, if in the Java code a 16 bytes key is used for encryption (AES-128) and in the PHP code aes-128-gcm and the same key is applied for decryption, then the decryption is successful.
In case of further problems please post the used Java version, the error message and also complete sample data, i.e. key, plaintext and ciphertext.
Sample data:
Plaintext (UTF8): The quick brown fox jumps over the lazy dog
Key (UTF8): 0123456789012345
The Java code provides (under JDK 11) the following ciphertext (this is of course different for each encryption because of the randomly generated IV):
Ciphertext (Base64): 8DcD/QwKeFG1u2N1ve3mtsX1Lq7js33ESTigT2GH6Lrqrckh5I4qzkJMG3rnuJ9CSFZ1jai8LTChe3tuIJSMLmMTbUQ9mB0=
IV (hex): f03703fd0c0a7851b5bb6375
This ciphertext can be decrypted by the PHP decrypt method using aes-128-cbc and the above key:
print(decrypt("8DcD/QwKeFG1u2N1ve3mtsX1Lq7js33ESTigT2GH6Lrqrckh5I4qzkJMG3rnuJ9CSFZ1jai8LTChe3tuIJSMLmMTbUQ9mB0=", "0123456789012345") . "\n");
Accordingly aesGcmEncrypt returns the same ciphertext if aes-128-cbc, the above key and the same IV are used (for the latter the randomly generated IV in the PHP code has to be replaced by the IV generated in the Java code, of course only for this test):
$iv = hex2bin('f03703fd0c0a7851b5bb6375'); // IV to use in aesGcmEncrypt
print(aesGcmEncrypt("The quick brown fox jumps over the lazy dog", "0123456789012345") . "\n");
I am trying to achieve ECIES encryption, for which below code is working.
X9ECParameters ecP = CustomNamedCurves.getByName("curve25519");
ECParameterSpec ecSpec = EC5Util.convertToSpec(ecP);
BigInteger d = new BigInteger("145642755521911534651321230007534120304391871461646461466464667494947990");
ECPrivateKeySpec priKeySpec = new ECPrivateKeySpec(
d, // d
ecSpec);
ECPoint Q = new FixedPointCombMultiplier().multiply(params.getG(), d.multiply(BigInteger.valueOf(-1)));
Q = Q.normalize();
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(
new ECPoint(Q.getAffineXCoord().toBigInteger(), Q.getAffineYCoord().toBigInteger()), // Q
ecSpec);
KeyFactory factTrial = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
BCECPrivateKey sKey = (BCECPrivateKey) factTrial.generatePrivate(priKeySpec);
PublicKey vKey = factTrial.generatePublic(pubKeySpec);
Cipher c = Cipher.getInstance("ECIESwithAES-CBC",BouncyCastleProvider.PROVIDER_NAME);
byte[] encodeBytes = c.doFinal(data.getBytes());
String encrypt = Base64.getEncoder().encodeToString(encodeBytes);
Cipher c2 = Cipher.getInstance("ECIESwithAES-CBC",BouncyCastleProvider.PROVIDER_NAME);
c2.init(Cipher.DECRYPT_MODE,sKey, c.getParameters());
byte[] decodeBytes = c2.doFinal(encodeBytes);
String deCrypt = new String(decodeBytes,"UTF-8");
Issue is the private key element 'd'. If I try to replace it with output of scrypt hash, private key fails to be converted in PrivateKey instance.
I have gone through net resources https://github.com/bcgit/bc-java/issues/251, https://crypto.stackexchange.com/questions/51703/how-to-convert-from-curve25519-33-byte-to-32-byte-representation, https://crypto.stackexchange.com/questions/72134/raw-curve25519-public-key-points.
Above resources suggest the way Bouncy Castle for Curve25519 interprets private key is different from the ways some internet resource suggest. In post https://crypto.stackexchange.com/questions/51703/how-to-convert-from-curve25519-33-byte-to-32-byte-representation there is mention as follows.
According to the curve25519 paper a x25519 public key can be represented in 32 bytes.
The x25519 library I'm using (bouncycastle) however gives me a 33 byte representation according to this standard.
I am very new to ECC, these resources are confusing me, the difference between lengths, the style of encoding big vs. little.
I have tried libSodium 'crypto_box_easy' and 'crypto_box_open_easy'
via its Java binding and it works all fine. The 32 byte scrypt output
is used by 'crypto_box_seed_keypair' to generate key pair which is
used for encryption process.
As I see some maths is involved here which I lack at present or I am failing to see the conversion.
I have to go this route Scrypt output -> key pair -> use for encryption
Using directly KeyGenerator from BC is working, but that utilises SecureRandom, but I need the output of Scrypt to behave as private key.
Questions:
I'll really appreciate someone helps me understand the difference between libSodium and Bouncy Castle approach. libSodium mentions it uses X25519. When I try to create X25519 key from 32 bytes, but BC Cipher(ECIESwithAES-CBC) then complaints it is not a EC Point, from this resource 'https://github.com/bcgit/bc-java/issues/251' it seems there are differences in that too (Curve25519 vs X25519).
The private key 'd', how to interpret it. I have seen these random values in Bouncy Castle documentation and test cases, is this simply a number in the prescribed range for valid keys? This number is treated (little vs. big endian) before creating BigInteger instance. I mean the raw value of 'd' in the my code example was converted from some other number?
The struggle between understanding different mechanism of Curve25519 and BC API itself, I am really confused.
Some pointers to further my research would be of great help.
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'
I'm working on some interoperable code for encrypting/decrypting strings between Java and node.js and have managed to get node.js to decrypt what Java has encrypted with this being the final part to successful decryption: the secret key.
To derive a secret key in Java, we write:
private static Key deriveSecretKey(String secretKeyAlgorithm, String secretKey, String salt) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_ALGORITHM);
KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), char2byte(salt), 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), secretKeyAlgorithm);
return secret;
}
Notice the key length passed to PBEKeySpec() is 128 here. In node.js, however, I get an "Invalid key length" if I try to use 128 and actually have to use 16 here instead:
crypto.pbkdf2(key_value, salt_value, 65536, 16, function(err, key) {
var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
// decipher.setAutoPadding(false);
var decoded = decipher.update(ciphertext, 'binary', 'utf8');
decoded += decipher.final('utf8');
console.log('Result: ' + decoded);
});
Console output:
Result: Super secret stuff -- right here.
Curious as to why the difference when specifying key lengths between these two functions. Thanks!
Normally, key sizes are defined in bits. However, most cryptographic libraries don't handle bit sizes that cannot be divided by 8 particularly well - the output is almost always in octets (8-bit bytes). So it is up to the designer of the API if the user has to specify the size in bits, or in the number of octets in the octet string (byte array).
The only way to really know why bits or bytes are being chosen is to ask the person who designed the library. In my own code, I do try to keep to (ad-hoc) standards - so bits for key sizes. If it's unclear from the context which is which, it is probably best to use names such as blockSizeBits or blockSizeBytes. Documentation may be of help too of course, but using specific identifiers is best in my opinion.
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.