Decrypting data key in Java AWS SDK yields gibberish - java

So I've been reading and reading and looking at examples and...failing miserably. Here's my situation:
I have a CMK in KMS and I've generated a data key, like so:
$ aws kms generate-data-key --key-id 64a62e3e-7e38-4f86-8ef2-3d00929e6260 --key-spec AES_256
{
"Plaintext": "+SjeaxtD5TIhOcY16+A2NA493MbxnYozbzZx4i3/BfA=",
"KeyId": "arn:aws:kms:us-west-2:040512153658:key/64a62e3e-7e38-4f86-8ef2-3d00929e6260",
"CiphertextBlob": "AQIDAHgrvfqfgn9D0tTUJOISzFCz7ejMPZ6/HGX0kGAlzKYZ7wEiyHdpuGaOjpq4UQazPAgeAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMU5JtbI6lxLOv/p4KAgEQgDsX97Pk+ywqLU2VymLRgDSz0exOyzRgLMgd7WEf3sLUh4GnbYllIrxNSdK/DSZrYUhBo78KYugnkTj89g=="
}
I then verify it by decrypting from the CLI:
$ aws kms decrypt --ciphertext-blob fileb://<(echo 'AQIDAHgrvfqfgn9D0tTUJOISzFCz7ejMPZ6/HGX0kGAlzKYZ7wEiyHdpuGaOjpq4UQazPAgeAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMU5JtbI6lxLOv/p4KAgEQgDsX97Pk+ywqLU2VymLRgDSz0exOyzRgLMgd7WEf3sLUh4GnbYllIrxNSdK/DSZrYUhBo78KYugnkTj89g==' | base64 -d) --query Plaintext
"+SjeaxtD5TIhOcY16+A2NA493MbxnYozbzZx4i3/BfA="
Lo and behold! I get the Plaintext value back all nice and clean. I then try to grind that same ciphertext blob through the SDK using Java with the following code:
.
.
.
final String encryptedCipherText = "AQIDAHgrvfqfgn9D0tTUJOISzFCz7ejMPZ6/HGX0kGAlzKYZ7wEiyHdpuGaOjpq4UQazPAgeAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMU5JtbI6lxLOv/p4KAgEQgDsX97Pk+ywqLU2VymLRgDSz0exOyzRgLMgd7WEf3sLUh4GnbYllIrxNSdK/DSZrYUhBo78KYugnkTj89g==";
final String expectedPlainText = "+SjeaxtD5TIhOcY16+A2NA493MbxnYozbzZx4i3/BfA=";
AWSKMS kmsClient;
String returnValue;
kmsClient = AWSKMSClientBuilder
.standard()
.withRegion("us-west-2")
.build();
ByteBuffer cipherTextBlob = ByteBuffer.wrap(Base64.getDecoder().decode(encryptedCipherText));
DecryptRequest decryptRequest = new DecryptRequest().withCiphertextBlob(cipherTextBlob);
ByteBuffer key = kmsClient.decrypt(decryptRequest).getPlaintext();
final byte[] bytes = new byte[key.remaining()];
key.duplicate().get(bytes);
String result = new String(bytes);
if (expectedPlainText.equals(result)) {
LOG.info("decrypted plaintext matches expected");
} else {
LOG.error("decrypted plaintext unexpected value: " + result);
}
.
.
.
And the LOG entry dumped out was:
23:08:33.210 [main] ERROR com.eyefinity.magicmissile.aws.AwsClientConfig - decrypted plaintext unexpected value: �(�k�2!9�5��64=���3o6q�-��
I've tried encoding the result with every Charset available to me, and no Charset produces my original Plaintext key. As near as I can tell from all the examples I've seen, my code is correct. So what am I doing wrong or what am I missing here? All I want is to end up with a Java String variable that contains "+SjeaxtD5TIhOcY16+A2NA493MbxnYozbzZx4i3/BfA=".

I stumbled on my own solution: I was SO CLOSE! All that's required in the code above to extract the same Plaintext value that is returned from KMS, and the ASCII string I received on the aws-cli command line when generating the datakey, is to take the byte array and Base64 encode it. So referencing my sample code above, all the way at the top, replace the line that reads...
String result = new String(bytes);
with something like this:
String result = Base64.getEncoder().encodeToString(bytes);

Related

Encryption in postgres using pgcrypto module and Decryption in java

I have created a trigger and a trigger function which invokes on every update operation on a table and encrypts a specific value as below:
create trigger project_trigger
before update
on projects
for each row
execute procedure project_function();
create or replace function project_function()
returns trigger as
$BODY$
begin
IF (TG_OP = 'UPDATE') THEN
NEW.title = armor(pgp_sym_encrypt(NEW.title, 'cipher-algo=aes256' ));
return NEW;
END IF;
end;
$BODY$
language plpgsql;
The above approach for encryption is working fine and an armored PGP encrypted value gets saved as below:
-----BEGIN PGP MESSAGE-----
ww0EBwMCneBsNZw1gYFq0jYB9y58EoTaRXWmDFqvQArWU5tZ+wS+7yAm9ycVUpkH1EzvYLbfRoDj
rqR83I0nGErHcLSLlAs=
=IYg8
-----END PGP MESSAGE-----
Decryption needs to be done at the application level for which I followed the following 2 steps:
Added bcpg-jdk15on and bcprov-jdk15on dependencies. (v1.47)
Implementation:
String key = "aes_key";
File file = new File("D:\\file.txt.asc"); //this file contains the PGP encrypted value as shown above
InputStream input = new FileInputStream(file);
byte[] byt = new byte[input.available()];
input.read(byt);
input.close();
Security.addProvider(new BouncyCastleProvider());
System.out.println(new String(ByteArrayHandler.decrypt(byt,
key.toCharArray())));
I keep getting the following exception while using the above approach to decrypt the value:
Exception in thread "main"
org.bouncycastle.openpgp.PGPDataValidationException: data check
failed. at
org.bouncycastle.openpgp.PGPPBEEncryptedData.getDataStream(Unknown
Source) at
org.bouncycastle.openpgp.examples.ByteArrayHandler.decrypt(Unknown
Source) at
abc.demo.encryption.SymmetricDecyption.main(SymmetricDecyption.java:59)
So can someone guide me to the appropriate approach to achieve decryption at the application level (not in the queries).
There are two problems. The PGPDataValidationException is caused by using a different pass-phrase for encryption and decryption. If you had used the correct pass-phrase, then you would have found that the Bouncy Castle example code is not fully functional.
The trigger is probably not what you intended. The call to pgp_sym_encrypt should look more like this:
create or replace function project_function()
returns trigger as
$BODY$
begin
IF (TG_OP = 'UPDATE') THEN
NEW.title = armor(pgp_sym_encrypt(NEW.title, 'my-secret-passphrase', 'cipher-algo=aes256, compress-algo=2' ));
return NEW;
END IF;
end;
$BODY$
language plpgsql;
The three input parameters to pgp_sym_encrypt are the text to be encrypted, the pass phrase from which the cipher key will be derived, and options. In your question, you omitted the pass phrase.
Second the BouncyCastle example code assumes that the plain text has been compressed. I have added RFC1950 compression (ZLIB) to the pgp_sym_encrypt.
With those changes to the trigger I get:
postgres=# update projects set title = 'My secret compressed title.';
UPDATE 1
postgres=# \t off
postgres=# select * from projects;
title
-----BEGIN PGP MESSAGE-----
ww0ECQMCuN3MyfrWhBt50lcBGbUtjOlTBxGpAFCl7aYEybhhXRJodDsikWxdLmOsXnE6vWr9mwd7
dGy7N1eE5VFmwI5N29eCNhEvG5U4YmVC7fV1A1sBeoJMtsO/nz2mi2jbFiZHlzo=
=s6uI
-----END PGP MESSAGE-----
(1 row)
postgres=#
Feeding that into a Java program:
String value = "-----BEGIN PGP MESSAGE-----\n"
+ "\n"
+ "ww0ECQMCuN3MyfrWhBt50lcBGbUtjOlTBxGpAFCl7aYEybhhXRJodDsikWxdLmOsXnE6vWr9mwd7\n"
+ "dGy7N1eE5VFmwI5N29eCNhEvG5U4YmVC7fV1A1sBeoJMtsO/nz2mi2jbFiZHlzo=\n"
+ "=s6uI\n"
+ "-----END PGP MESSAGE-----\n";
String key = "my-secret-passphrase";
byte[] byt = value.getBytes(StandardCharsets.UTF_8);
Security.addProvider(new BouncyCastleProvider());
System.out.println(new String(ByteArrayHandler.decrypt(byt, key.toCharArray()), StandardCharsets.UTF_8));
Produces the output:
My secret compressed title.
Exactly as desired.
If you want to not compress the plain text before encrypting it, then you can look at the example PBEFileProcessor as this handles both compressed and uncompressed data, or you can just use this code:
public static byte[] decrypt(
byte[] encrypted,
char[] passPhrase
) throws IOException, PGPException {
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(PGPUtil.getDecoderStream(new ByteArrayInputStream(encrypted)));
// Find the encrypted data list. The first object might be a PGP marker packet, or the actual data list
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
// Do the decryption
PGPPBEEncryptedData pbe = (PGPPBEEncryptedData) enc.get(0);
InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(
new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(passPhrase)
);
// Process the decrypted data. It may be compressed, or it may be literal
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(clear);
o = pgpFact.nextObject();
if (o instanceof PGPCompressedData) {
// Need to decompress the data
PGPCompressedData cData = (PGPCompressedData) o;
pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
o = pgpFact.nextObject();
}
// We should have the literal data now, so convert it into bytes
PGPLiteralData ld = (PGPLiteralData) o;
return Streams.readAll(ld.getInputStream());
}
Finally, when decrypting in postgresql you do not need to specify whether the plain text was compressed, nor how it was encrypted, as the PGP data specifies this, so you can do:
select pgp_sym_decrypt(dearmor(title), 'my-secret-passphrase') from projects;

android base64 encode and decode in php

hi i'm trying to decode two strings then use the return result (byte[]) then put it in a Biginteger constructor Like this :
BigInteger bigInteger1 = new BigInteger(1, Base64.decode(myString1,0));
BigInteger bigInteger2 = new BigInteger(1, Base64.decode(myString2,0));
then put this BigIntegers on a java.security.KeyFactory class to create a RSAPublicKey like This :
KeyFactory.getInstance(ALG).generatePublic(new RSAPublicKeySpec(bigInteger1,bigInteger2));
then use my public key to encode a string Like this :
public static String encrypt(PublicKey publicKey, String str) {
Cipher instance = Cipher.getInstance(ALG);
instance.init(1, publicKey);
Base64.encodeToString(instance.doFinal(str.getBytes()), 2);
}
on PHP. I achieve this goal with android but when I want to do it with PHP I have a lot of problems even on the start when I want to decode my string in PHP with this code :
$encoded = base64_decode($base,true);
$decoded = utf8_decode(base64_decode($encoded));
I will get this string:??2?]????5N?[??S
but in android, the decoded string is totally different and always stay the same result
I tried to do this job on JSP but it's really hard to learn a new language and I don't have the time.
Can I do this project in spring boot? I have the codes for java
please, somebody, help me.
You're decoding string twice. You should try :
$encoded = base64_encode($base); // and not base64_decode
$decoded = base64_decode($encoded); // will be $base
utf8_decode is converting a string with ISO-8859-1 characters encoded with UTF-8 to single-byte ISO-8859-1. Are you sure you're needing it ?
In PHP, you can use intval to performs BigInteger() but I'm not sure you won't be facing an integer overflow.
Finally, OpenSSL library will certainly do the job for key generation and encryption.

java - How to parse String to a signature byte[]

I'm trying to obtain the byte[] that represents the signature that is in a String but keep receiving "java.security.SignatureException: invalid encoding for signature"
What i'm trying to do is to send a signed string and veryfying it at server, here is my code:
Here is where i sign my string to send it via Web Service:
Signature signer = Signature.getInstance("DSA");
signer.initSign(signerKey);
signer.update(someString)
byte[] signature = signer.sign();
newToken = someString + Base64.getEncoder().encodeToString(signature);
Here is the server-side code to obtain the string and verify the whole thing with it:
byte [] sig = Base64.getDecoder().decode(stringSignature);
Signature verifier = Signature.getInstance("DSA");
verifier.initVerify(verifierPubKey);
verifier.update(token);
verified = verifier.verify(signature);
So, what's the best way to obtain a signature from a string that actually represents the signature i'm trying to verify?
Thanks.
I think this:
verified = verifier.verify(signature);
should be changed to this:
verified = verifier.verify(sig);
I'm guessing signature is the Base64 encoding or something, rather than the raw bytes you need.

HMC SHA1 hash - Java producing different hash output than C#

This is a follow up to this question, but I'm trying to port C# code to Java instead of Ruby code to C#, as was the case in the related question. I am trying to verify the encrypted signature returned from the Recurly.js api is valid. Unfortunately, Recurly does not have a Java library to assist with the validation, so I must implement the signature validation myself.
Per the related question above (this), the following C# code can produce the hash needed to validate the signature returned from Recurly:
var privateKey = Configuration.RecurlySection.Current.PrivateKey;
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(dataToProtect));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
Recurly provides the following example data on their signature documentation page:
unencrypted verification message:
[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
private key:
0123456789ABCDEF0123456789ABCDEF
resulting signature:
0f5630424b32402ec03800e977cd7a8b13dbd153-1312701386
Here is my Java implementation:
String unencryptedMessage = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
String encryptedMessage = getHMACSHA1(unencryptedMessage, getSHA1(privateKey));
private static byte[] getSHA1(String source) throws NoSuchAlgorithmException, UnsupportedEncodingException{
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] bytes = md.digest(source.getBytes("UTF-8"));
return bytes;
}
private static String getHMACSHA1(String baseString, byte[] keyBytes) throws GeneralSecurityException, UnsupportedEncodingException {
SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] bytes = baseString.getBytes("ASCII");
return Hex.encodeHexString(mac.doFinal(bytes));
}
However, when I print out the encryptedMessage variable, it does not match the message portion of the example signature. Specifically, I get a value of "c8a9188dcf85d1378976729e50f1de5093fabb78" instead of "0f5630424b32402ec03800e977cd7a8b13dbd153".
Update
Per #M.Babcock, I reran the C# code with the example data, and it returned the same output as the Java code. So it appears my hashing approach is correct, but I am passing in the wrong data (unencryptedMessage). Sigh. I will update this post if/when I can determine what the correct data to encrypt is- as the "unencrypted verification message" provided in the Recurly documentation appears to be missing something.
Update 2
The error turned out to be the "unencrypted verification message" data/format. The message in the example data does not actually encrypt to the example signature provided- so perhaps outdated documentation? At any rate, I have confirmed the Java implementation will work for real-world data. Thanks to all.
I think the problem is in your .NET code. Does Configuration.RecurlySection.Current.PrivateKey return a string? Is that value the key you expect?
Using the following code, .NET and Java return identical results.
.NET Code
string message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
string privateKey = "0123456789ABCDEF0123456789ABCDEF";
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(message));
Console.WriteLine(" Message: {0}", message);
Console.WriteLine(" Key: {0}\n", privateKey);
Console.WriteLine("Key bytes: {0}", BitConverter.ToString(hashedKey).Replace("-", "").ToLower());
Console.WriteLine(" Result: {0}", BitConverter.ToString(hash).Replace("-", "").ToLower());
Result:
Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
Key: 0123456789ABCDEF0123456789ABCDEF
Key bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
Result: c8a9188dcf85d1378976729e50f1de5093fabb78
Java
String message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] keyBytes = md.digest(privateKey.getBytes("UTF-8"));
SecretKey sk = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(sk);
byte[] result = mac.doFinal(message.getBytes("ASCII"));
System.out.println(" Message: " + message);
System.out.println(" Key: " + privateKey + "\n");
System.out.println("Key Bytes: " + toHex(keyBytes));
System.out.println(" Results: " + toHex(result));
Result:
Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
Key: 0123456789ABCDEF0123456789ABCDEF
Key Bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
Results: c8a9188dcf85d1378976729e50f1de5093fabb78
I suspect the default encoding of the values you're working on may be different. As they do not have it specified, they will use the default encoding value of the string based on the platform you're working on.
I did a quick search to verify if this was true and it was still inconclusive, but it made me think that strings in .NET default to UTF-16 encoding, while Java defaults to UTF-8. (Can someone confirm this?)
If such's the case, then your GetBytes method with UTF-8 encoding is already producing a different output for each case.
Based on this sample code, it looks like Java expects you to have not already SHA1'd your key before creating a SecretKeySpec. Have you tried that?

Can you get this same Java SHA-1 in PHP please?

I find myself in a need to change website platforms from Java to PHP but I'd like to keep all my user's passwords...
I had this code do the password hashing prior to writting the hashed value as the password to the website:
MessageDigest md = null;
md = MessageDigest.getInstance("SHA");
md.update(plaintext.getBytes("UTF-8"));
byte raw[] = md.digest();
hash = new Base64().encodeToString(raw).replaceAll("\n", "").replaceAll("\r", "");
I think the Java code did SHA-1 hashing of the password but just prior to that it was byte encoded to UTF-8 and afterwards it was Base64 encoded.
I'd like to have a PHP code do the same, i.e. return the same value of a hash for the same password as in Java, only it seems that the PHP code doing SHA-1 hashing I have won't return the same SHA(-1, not Base64 encoded, I think?) value when compared to a Java Base64 decoded value of the hash...could it have something to do with the fact that my passwords in PHP are not UTF-8 byte encoded first (and how can I do that in PHP) please?
p.s.
Another strange thing...my passwords in Java are all 28characters long (usually something like this rnwn4zTNgH30l4pP8V05lRVGmF4=)...but the Base64().decode(hash) value of those password hashes is 10 characters long (an example [B#14e1f2b).
I thought Base64 did an additional 1 character to each 3 charters (28 or 27, excluding the padding = charter, is much more that a third larger than those 10 charcters) so am I doing the decoding call wrong somehow maybe???
And on top of all that the SHA-1 password hashed values in PHP are 40 characters long (in a UTF-8 mysql database) like so dd94709528bb1c83d08f3088d4043f4742891f4f?
[B#14e1f2b is definitely not a hash. It's a result of implicit conversion from byte[] to String.
It looks like you do something like this:
String decodedHash = Base64().decode(hash); // Produces [B#14e1f2b
However, the correct representation of the hash is a byte array:
byte[] decodedHash = Base64().decode(hash);
What I normally do with Java to compute a SHA-1 hash that is exactly identical to the PHP sha1() function is the following. The key is that toHexString is used to show the raw bytes in a printable way. If you use the PHP function and want to obtain the same result of your convoluted process, you need to use the parameter $raw_output to true in PHP to get the raw bytes and apply Base64. Full source code.
/**
* Compute a SHA-1 hash of a String argument
*
* #param arg the UTF-8 String to encode
* #return the sha1 hash as a string.
*/
public static String computeSha1OfString(String arg) {
try {
return computeSha1OfByteArray(arg.getBytes(("UTF-8")));
} catch (UnsupportedEncodingException ex) {
throw new UnsupportedOperationException(ex);
}
}
private static String computeSha1OfByteArray(byte[] arg) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(arg);
byte[] res = md.digest();
return toHexString(res);
} catch (NoSuchAlgorithmException ex) {
throw new UnsupportedOperationException(ex);
}
}
private static String toHexString(byte[] v) {
StringBuilder sb = new StringBuilder(v.length * 2);
for (int i = 0; i < v.length; i++) {
int b = v[i] & 0xFF;
sb.append(HEX_DIGITS.charAt(b >>> 4)).append(HEX_DIGITS.charAt(b & 0xF));
}
return sb.toString();
}
PHP's sha1() encodes each byte of the output as hexadecimal by default, but you can get the raw output by passing true as the second argument:
$digest = sha1($password, true); // This returns the same string of bytes as md.digest()
Then pass the digest to base64_encode and you are done:
base64_encode(sha1($password, true));
This returns the exact same SHA-1 hash as your java code.

Categories

Resources