Get the sha1-hashed value from XML signature value - java

I need someone to help me understand XML digital signature method rsa-sha1. I suppose the signature value = RSA-encrypt(sha1(signedInfo), privatekey).
Note Base64.encode(sha1(signedInfo)) contains 28 characters. So I think Base64.encode(RSA-decrypt(signaturevalue), publickey) should return 28 characters as well. However, I actually got a 48-character string.
Base64 base64 = new Base64();
byte[] encrypted = base64.decode(signatureValue);
try {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, getX509Cert().getPublicKey());
byte[] cipherText = cipher.doFinal(encrypted);
System.out.println(base64.encodeToString(cipherText));
//print out MCEwCQYFKw4DAhoFAAQU0G+7jFPydS/sWGO1QPjB0v3XTz4=
//which contains 48 characters.
}
catch (Exception ex){
ex.printStackTrace();
}
Signature method as indicated in XML file
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>

RSA signing is not actually the same as encrypting with the private key, but JCE promotes this mistake by allowing 'backwards' operations in Cipher for RSA (only) which actually do PKCS1-v1_5 signature and recovery instead of encryption and decryption as they were designed to.
For the original standardized RSA signature scheme in PKCS1 through v1.5, now retronymed RSASSA-PKCS1-v1_5, the value that is padded (with 'type' 01 multiple FFs and one 00) and modexp'ed with the private key is not just the hash but an ASN.1 structure containing the hash. See the encoding operation EMSA-PKCS1-v1_5 in section 9.2 of rfc8017 or rfc3447 or 9.2.1 in rfc2437, especially step 2 and (for the newer two versions) 'Notes' item 1.
Dupe Using SHA1 and RSA with java.security.Signature vs. MessageDigest and Cipher
and Separate digest & signing using java security provider

Related

Unable to decrypt aes-256-gcm encrypted data in java

I have encrypted a file by using OpenSSL aes-256-gcm. As aes-256-gcm not directed supported by command line I have installed LibreSSL and I am able to use the below command to encrypt the data of a file.
openssl enc -aes-256-gcm -K 61616161616161616161616161616161 -iv 768A5C31A97D5FE9 -e -in file.in -out file.out
I need to decrypt the data of file.out in Java and I am unable to do that.
Sample code :
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
String key = "61616161616161616161616161616161";
byte[] IV = "768A5C31A97D5FE9".getBytes();
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
// Create GCMParameterSpec
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText); // for the data by reading file.out
However, I am getting an exception saying javax.crypto.AEADBadTagException: Tag mismatch!
That should't work. Commandline openssl enc doesn't support AEAD ciphers/modes, although early versions of 1.0.1 (below patch h, in 2012-2014) failed to catch if you incorrectly specified such a cipher and silently produced wrong output. If you are actually using LibreSSL and not OpenSSL, it appears to have inherited this problem and not fixed it, even though the whole point of the LibreSSL project was that they were going to fix all the bugs caused by the incompetent OpenSSL people.
If this were a cipher that worked correctly in OpenSSL (and also Java), like aes-256-ctr, then your only problem would be that openssl enc -K -iv take their arguments in hex (suitable for a shell context), whereas Java crypto is called from code that can handle binary data and expects its arguments in that form. As a result the values you provide to OpenSSL are actually 16 bytes (128 bits) and 8 bytes (64 bits), not 256 bits and 128 bits as they should be (for CTR; for GCM an IV of 96 bits would be correct, but as noted GCM won't work here). openssl enc automatically pads -K -iv with (binary) zeros, but Java doesn't. Thus you would need something more like
byte[] key = Arrays.copyOf( javax.xml.bind.DatatypeConverter.parseHexBinary("61616161616161616161616161616161"), 32);
// Arrays.copyOf zero-pads when expanding an array
// then use SecretKeySpec (key, "AES")
// and IVParameterSpec (iv) instead of GCMParameterSpec
// but after Java8 most of javax.xml is removed, so unless you
// are using a library that contains this (e.g. Apache)
// or have already written your own, you need something like
byte[] fromHex(String h){
byte[] v = new byte[h.length()/2];
for( int i = 0; i < h.length(); i += 2 ) v[i] = Integer.parseInt(h.substring(i,i+2),16);
return v;
}
Compare AES encrypt with openssl command line tool, and decrypt in Java and Blowfish encrypt in Java/Scala and decrypt in bash (the latter is the reverse direction, but the need to match is the same)

How to generate an AES key with AES/CBC/PKCS5Padding for encrypting and decrypting

I'm trying to create a AES key with this code
public static SecretKey generateSecretKey() {
KeyGenerator generator;
try {
generator = KeyGenerator.getInstance(StaticHandler.AES_KEY_MODE); // Is "AES"
generator.init(StaticHandler.AES_KEY_SIZE); // The AES key size in number of bits // Is "128"
return generator.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
however using this code for encrypting/decrypting
public static String encrypt(String data, SecretKey secret, Charset charset) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
return new String(cipher.doFinal(data.getBytes()), charset);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String decrypt(String data, #NonNull SecretKey secret, Charset charset) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret);
return new String(cipher.doFinal(data.getBytes()), charset);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
gets the error
java.security.InvalidKeyException: Parameters missing
I'm guessing I need to add some salt, though I don't know how to do that with a generated key. I would like to stray away from generating a password but if it's a securely generated password I wouldn't mind.
Edit: Just an after thought, should I use GCM or CBC encryption if I'm sending packets through the network? Remember I'm using randomly generated keys and I am not going to keep them for sessions, randomly generated per client and server session.
No, you don't need salt and your key is actuallly fine. CBC mode requires an IV (Initialization Vector), see wikipedia, and IV should be different for each piece of data encrypted, but each decryption must use the same value as the corresponding encryption did. (added) For CBC, though not some other modes, it is also vital for security that IVs not be predictable by an adversary; the simplest and most common way to achieve both uniqueness and unpredictability is to use a secure Random Number (aka Bit) Generator such as Java's SecureRandom. If you want to know about other methods, that is not really a programming issue and is better suited on crypto.SX or security.SX, where there are already several Qs.
You can either generate the IV explicitly and specify it to both encrypt and decrypt, or allow the encrypt operation to generate the IV itself, fetch it from the encrypt Cipher, and specify it to the decrypt Cipher. In either case the encryptor must provide the value the decryptor will use; a common approach is to simply concatenate the IV with the ciphertext (making it very easy to keep them matched up properly) but again there are other approaches discussed on crypto and security. See https://docs.oracle.com/en/java/javase/11/security/java-cryptography-architecture-jca-reference-guide.html in the sections named "Initializing a Cipher Object" (the two paragraphs just after the boxed block of method declarations) and "Managing Algorithm Parameters".
Also don't store ciphertext in a String. Java String is designed to handle valid characters not arbitrary bytes. 'Decoding' ciphertext to a String and 'encoding' it back to binary will almost always lose or alter some of the data, especially if you allow the Charset to differ at the two ends, and with modern cryptography any change at all to the ciphertext will destroy all or much of your data. Since ciphertext is bytes, it is best to handle it as byte[]; if that is not possible because you want to put it in something that is characters like a URL, use one of the many schemes designed to encode arbitrary bytes to text so that they can be recovered correctly: base64 (3 or 4 major variants, plus many minor ones), base32, hexadecimal/base16, URL 'percent' encoding, MIME quoted-printable, yencode, Kermit, PPP, etc. j8+ java.util.Base64 provides the newer base64 variants (i.e. not uuencode).
Conversely, although 'plaintext' in modern crypto can really be any form of data, if yours truly is text and belongs in a String you should encode it using a suitable Charset before encrypting, and decode using the same Charset after decrypting, i.e.
byte[] ctext = encCipher.doFinal (input.getBytes(charset));
...
String output = new String (decCipher.doFinal (ctext), charset);
While the 'best' Charset may vary depending on your data, if you don't know what the data will be or don't want to bother analyzing it, UTF-8 is reasonably good for most text data and very popular and standard.

RSA encrypt in php to RSA decrypt in JAva

at the moment im trying to encrypt with rsa in php with a public key generated in an android app and then decrypt in android app again.
My code to generate the keys in android is:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
With that keys i can en- and decrypt very well. The pub key looks like this:
OpenSSLRSAPublicKey{modulus=9ee9f82dd8429d9fa7f091c1d375b9c289bcf2c39ec57e175a2998b4bdd083465ef0fe6c7955c821b7e883929d017a9164a60290f1622f664a72096f5d2ffda7c7825c3d657c2d13d177445fa6cdd5d68b96346006a96040f5b09baae56d0c3efeaa77d57602f69018f5cefd60cb5c71b6b6f8a4b0472e8740367266917d8c13,publicExponent=10001}
In php im taking the modulus and exponent, creating a encrypted string with phpseclib 1.0
$rsa = new Crypt_RSA();
// $rsa->createKey();
$m = "9ee9f82dd8429d9fa7f091c1d375b9c289bcf2c39ec57e175a2998b4bdd083465ef0fe6c7955c821b7e883929d017a9164a60290f1622f664a72096f5d2ffda7c7825c3d657c2d13d177445fa6cdd5d68b96346006a96040f5b09baae56d0c3efeaa77d57602f69018f5cefd60cb5c71b6b6f8a4b0472e8740367266917d8c13";
$e = "10001";
$data = "hallo";
$modulus = new Math_BigInteger($m, 16);
$exponent = new Math_BigInteger($e, 16);
$rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
$messageEncrypt = $rsa->encrypt($data);
In Android again, im loading the key, and decrypting it like this:
Cipher cipher1 = Cipher.getInstance("RSA");
cipher1.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher1.doFinal(encrypted.getBytes());
String decrypted = new String(decryptedBytes);
Im always getting a wrong decrypted plaintext or a " Caused by: java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" error message from Android.
What i think: The problem is the encoded transfer. That php outputs a different encoded version as java uses. So I tried a lot of different ways. I tried to convert the output to String/bin/hex/byte. Then transfer it, with socket or with copy+paste directly in the Code. Convert it back from hex/bin... to a byte[] and try to decode it. Nothing works...
Anyone has a solution for this?
Since you're not specifying the encryption mode with phpseclib what that means is that you're using the (more secure and less common) OAEP encryption mode. My guess is that Java is using PKCS1 encryption by default ($rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);).
That said, with OAEP mode and the key that you're using (a 1024-bit key; 128 bytes), the limit is 86 bytes. The limit with PKCS1 mode is 117 bytes.
phpseclib 1.0 / 2.0 might not give errors because phpseclib tries to be all user friendly and will split the string up into chunks of the max size and will encrypt each chunk separately. It's unlikely that Java does that.

What's the detail in "SHA1withRSA"?

Innocently, I thought "SHA1withRSA algorithm" was simply operating the plainText with "SHA1", and use RSA/pkcs1padding to encrypt the result of "SHA1"。However, I found I was wrong until I wrote some java code to test what I thought.
I use RSA publickey to decrypt the signature which I use the corresponding privatekey to sign with "SHA1withRSA algorithm" . But I found the result is not equal to "SHA1(plainText)", below is my java code:
String plaintext= "123456";
Signature signature=Signature.getInstance("SHA1withRSA",new BouncyCastleProvider());
signature.initSign(pemPrivatekey);
signature.update(plaintext.getBytes());
byte[] sign = signature.sign();
//RSA decode
byte[] bytes = RsaCipher.decryptByRsa(sign, pemPublickey);
String rsaDecodeHex=Hex.toHexString(bytes);
System.out.println(rsaDecodeHex.toLowerCase());
String sha1Hex = Hash.getSha1(plaintext.getBytes());
System.out.println(sha1Hex);
//rsaDecodeHex!=sha1Hex
Easy to find that rsaDecodeHex!=sha1Hex, where
rsaDecodeHex=3021300906052b0e03021a050004147c4a8d09ca3762af61e59520943dc26494f8941b
and
sha1Hex=7c4a8d09ca3762af61e59520943dc26494f8941b 。
So, What's the detail in "SHA1withRSA" ?
The digital signature algorithm defined in PCKS#1 v15 makes a RSA encryption on digest algorithm identifier and the digest of the message encoded in ASN.1
signature =
RSA_Encryption(
ASN.1(DigestAlgorithmIdentifier + SHA1(message) ))
See (RFC2313)
10.1 Signature process
The signature process consists of four steps: message digesting, data
encoding, RSA encryption, and octet-string-to-bit-string conversion.
The input to the signature process shall be an octet string M, the
message; and a signer's private key. The output from the signature
process shall be a bit string S, the signature.
So your rsaDecodeHex contains the algorithm identifier and the SHA1 digest of plainText

Cipher: What is the reason for IllegalBlockSizeException?

I have observed the following when I worked with Cipher.
Encryption code:
Cipher aes = Cipher.getInstance("AES");
aes.init(Cipher.ENCRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
Decryption code :
Cipher aes = Cipher.getInstance("AES");
aes.init(Cipher.DECRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
I get IllegalBlockSizeException ( Input length must be multiple of 16 when ...) on running the Decrypt code.
But If I change the decrypt code to
Cipher aes = Cipher.getInstance("AES/ECB/PKCS5Padding"); //I am passing the padding too
aes.init(Cipher.DECRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
It works fine.
I understand that it is in the pattern algorithm/mode/padding. So I thought it is because I didn't mention the padding. So I tried giving mode and padding during encryption,
Encryption code:
Cipher aes = Cipher.getInstance("AES/ECB/PKCS5Padding");//Gave padding during encryption too
aes.init(Cipher.ENCRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
Decryption code :
Cipher aes = Cipher.getInstance("AES/ECB/PKCS5Padding");
aes.init(Cipher.DECRYPT_MODE, generateKey());
byte[] ciphertext = aes.doFinal(rawPassword.getBytes());
But it fails with IllegalBlockSizeException.
What is the reason, why the exception and what is exactly happening underneath.
If anyone can help? Thanks in advance
UPDATE
Looks like the issue is with the string I am encrypting and decrypting. Because, even the code that I said works, doesn't always work. I am basically encrypting UUIDs (eg : 8e7307a2-ef01-4d7d-b854-e81ce152bbf6). It works with certain strings and doesn't with certain others.
The length of encrypted String is 64 which is divisible by 16. Yes, I am running it on the same machine.
Method for secret key generation:
private Key generateKey() throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA");
String passphrase = "blahbl blahbla blah";
digest.update(passphrase.getBytes());
return new SecretKeySpec(digest.digest(), 0, 16, "AES");
}
During decryption, one can only get an IllegalBlockSizeException if the input data is not a multiple of the block-size (16 bytes for AES).
If the key or the data was invalid (but correct in length), you would get a BadPaddingException because the PKCS #5 padding would be wrong in the plaintext. Very occasionally the padding would appear correct by chance and you would have no exception at all.
N.B. I would recommend you always specify the padding and mode. If you don't, you are liable to be surprised if the provider changes the defaults. AFAIK, the Sun provider converts "AES" to "AES/ECB/PKCS5Padding".
Though I haven't fully understood the internals, I have found what the issue is.
I fetch the encrypted string as a GET request parameter. As the string contains unsafe characters, over the request the string gets corrupted. The solution is, to do URL encoding and decoding.
I am able to do it successfully using the URLEncoder and URLDecoder.
Now the results are consistent. Thanks :)
I would be grateful if anyone can contribute more to this.

Categories

Resources