How to verify xmldsig signature using java.security - java

I need to verify document with enveloped xml-dsig signature using java.security package.
After loading I unmarshal document and have object of Signature according to xsd - http://www.w3.org/2000/09/xmldsig#
Then:
#Service
public class XmlSignatureCheckerImpl implements XmlSignatureChecker {
private static final String ENCRYPTION_ALGORITHM = "RSA";
private static final String HASH_ENCRYPTION_ALGORITHM = "SHA1withRSA";
#Override
#Nullable
public PublicKey getPublicKey(byte[] exp, byte[] mod) {
BigInteger modulus = new BigInteger(1, mod);
BigInteger exponent = new BigInteger(1, exp);
RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, exponent);
KeyFactory fact;
try {
fact = KeyFactory.getInstance(ENCRYPTION_ALGORITHM);
return fact.generatePublic(rsaPubKey);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
#Override
#Nullable
public Boolean verify(byte[] message, byte[] signature, PublicKey publicKey) {
final Signature sig;
try {
sig = Signature.getInstance(HASH_ENCRYPTION_ALGORITHM);
sig.initVerify(publicKey);
sig.update(message);
boolean verify = sig.verify(Base64.encodeBase64Chunked(signature));
return verify;
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
}
Call getPublicKey and verify, as a result I got signature length mismatch, if I did't encode signature I got no mismatch, but also verification is false, but I use test data which is completely valid. Give up with finding error, help me. please.
File encoding is UFT-8.

Have you look at official documentation? Seems like working with the sign factory is a bit more convenient http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html
Also, I've found these examples if it will be helpful https://www.java-tips.org/java-ee-tips-100042/158-xml-digital-signature-api/1473-using-the-java-xml-digital-signature-api.html

Related

Encrypting properties of persistent objects

I am building an application that persists patient data. In order for the data to remain searchable, the identifiers and names need to be left un-encrypted(?). However I am planning to encrypt all other fields like address, phone, email, family members details and so on. I am using an AttributeConverter for this:
#Converter
public class AttributeEncryptor implements AttributeConverter<String, String> {
private static final String AES = "AES";
private static final byte[] encryptionKey = "big-secret".getBytes();
private final Cipher encryptCipher;
private final Cipher decryptCipher;
public AttributeEncryptor() throws Exception {
Key key = new SecretKeySpec(encryptionKey, AES);
encryptCipher = Cipher.getInstance(AES);
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
decryptCipher = Cipher.getInstance(AES);
decryptCipher.init(Cipher.DECRYPT_MODE, key);
}
#Override
public String convertToDatabaseColumn(String attribute) {
try {
return Base64.getEncoder().encodeToString(encryptCipher.doFinal(attribute.getBytes()));
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalArgumentException(e);
}
}
#Override
public String convertToEntityAttribute(String dbData) {
try {
return new String(decryptCipher.doFinal(Base64.getDecoder().decode(dbData)));
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalArgumentException(e);
}
}
}
Is this the best approach? Are there other preferred / alternative options?
This is a good post I came across, discussing various options and their Pros/ Cons.
https://stackoverflow.com/a/43779197/5417843

Porting python PKCS1_v1_5 encryption code to java generating different signatures

I am trying to port my working python code for generating signature to java but they both are producing different signatures. Here is my code for python which is generating the signature.
import pdb
pdb.set_trace()
signer = PKCS1_v1_5.new(priv_key)
digest = SHA256.new()
digest.update(message)
val = signer.sign(digest)
return val
I am calling this function using this statement
signature = b64encode(sign(msg1, private))
Here the msg1 is
msg1 = 'test'.encode("utf8")
and private is the private key which I am importing using
RSA.importKey("<Location of key>"
I am trying to write a similar java code for implementing the same functionality with this given code
public static void main(String[] args) throws Exception {
String payload = "test";
String dig = makeDigest( payload, "SHA-256");
Key k = getPrivateKey("private_key.der");
String signature = encrypt(dig, "RSA", k);
System.out.print(signature);
}
public static String makeDigest(String payload, String digestAlgo) {
String strDigest = "";
try {
MessageDigest md = MessageDigest.getInstance(digestAlgo);
byte[] p_b = payload.getBytes("UTF-8");
md.update(p_b);
byte[] digest = md.digest();
char[] encoded = Hex.encodeHex(digest);
strDigest = new String(encoded);
System.out.println(strDigest);
}
catch (Exception ex) {
ex.printStackTrace();
}
return strDigest;
}
public static String encrypt(String sha, String encryptAlgo, Key k) {
String strEncrypted = "";
try {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(encryptAlgo,"BC");
cipher.init(Cipher.ENCRYPT_MODE, k);
byte[] encrypted = cipher.doFinal(sha.getBytes("UTF-8"));
//System.out.println(new String(encrypted));
byte[] encoded = Base64.getEncoder().encode(encrypted);
strEncrypted = new String(encoded);
}
catch (Exception ex) {
ex.printStackTrace();
}
return strEncrypted;
}
public static PrivateKey getPrivateKey(String filename)
throws Exception {
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
Security.addProvider(new BouncyCastleProvider());
KeyFactory kf = KeyFactory.getInstance("RSA","BC");
return kf.generatePrivate(spec);
}
Both thee codes are generating different signatures. I feel something I am doing wrong in selecting the algorithm on Java side. I have tried with
RSA/NONE/PKCS1Padding
and
RSA/ECB/PKCS1Padding
but still, it is different signature as compared to python code.
Any lead on this will be very helpful.

Unable to store secp256k1 generated private key using Amazoun CloudHSM Java Library

When I try to import a secp256k1 private key into my CloudHSM instance, I get the error "java.security.InvalidKeyException: The key is an instance of CaviumKey and cannot be imported." Importing a secp256r1 private key works fine.
I'm using the provided examples as guidance (https://github.com/aws-samples/aws-cloudhsm-jce-examples ) and it seems that the exportKey method doesn't convert the key to a privateKey, but returns a CaviumKey instead (I've linked to the line in the method below).
https://github.com/aws-samples/aws-cloudhsm-jce-examples/blob/master/src/main/java/com/amazonaws/cloudhsm/examples/KeyUtilitiesRunner.java#L278
/**
* Export an existing persisted key.
* #param handle The key handle in the HSM.
* #return Key object
*/
private static Key exportKey(long handle) {
try {
byte[] keyAttribute = Util.getKeyAttributes(handle);
CaviumKeyAttributes cka = new CaviumKeyAttributes(keyAttribute);
System.out.println(cka.isExtractable());
byte[] encoded = Util.exportKey( handle);
if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_AES) {
Key aesKey = new SecretKeySpec(encoded, 0, encoded.length, "AES");
return aesKey;
}
else if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_RSA && cka.getKeyClass() == CaviumKeyAttributes.CLASS_PRIVATE_KEY) {
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(encoded));
return privateKey;
}
else if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_RSA && cka.getKeyClass() == CaviumKeyAttributes.CLASS_PUBLIC_KEY) {
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(encoded));
return publicKey;
} else if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_EC && cka.getKeyClass() == CaviumKeyAttributes.CLASS_PRIVATE_KEY) {
PrivateKey privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(encoded));
return privateKey;
}
else if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_EC && cka.getKeyClass() == CaviumKeyAttributes.CLASS_PUBLIC_KEY) {
PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(encoded));
return publicKey;
}
} catch (BadPaddingException | CFM2Exception e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
This function returns a PrivateKey that is still an instance of CaviumKey, which throws an error when trying to import into the HSM via the Cavium libraries.
Does anyone have an idea of why this is happening or how I could fix this?
Instead of EC, did you tried with CaviumKeyAttributes.KEY_TYPE_ECDSA ?

Bad Padding Exception JPA AttributeConverter / MultiThreading issue

I have sensitive data which needs to be encrypted when saving in the database so that no one can read it even if they look into the database. So the approach I followed is to use AttributeConverter.
I created a class like this
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter
public class SensitiveDataConverter implements AttributeConverter<String, String> {
private final EncryptDecryptUtils encryptDecryptUtils;
/**
* Instantiates a new vulnerable data converter.
* Not using {#link javax.inject.Inject} because #Inject doesn't
* work with AttributeConverter
*
*/
public SensitiveDataConverter() {
this.encryptDecryptUtils = EncryptDecryptUtils.getInstance();
}
#Override
public String convertToDatabaseColumn(String attribute) {
return attribute != null ? encryptDecryptUtils.encrypt(attribute): null;
}
#Override
public String convertToEntityAttribute(String dbData) {
return dbData != null ? encryptDecryptUtils.decrypt(dbData): null;
}
And In my Entity class I have sensitive fields marked as
#Column
#Convert(converter = SensitiveDataConverter.class)
private String sensitiveData;
There are more than one column with such sensitive data.
My Encrypt/Decrypt code looks like this. I am using 128 length Key.
private static final byte[] IV = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
private static final IvParameterSpec IV_PARAMETER_SPEC = new IvParameterSpec(IV);
private static final int KEY_LENGTH = 128;
private final SecretKeySpec secretKeySpec;
private final Cipher cipher;
private static volatile EncryptDecryptUtils instance;
private String secretKey = "someSecretkey";
private String salt = "someSalt";
private EncryptDecryptUtils() {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(StandardCharsets.UTF_8),
65536, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
secretKeySpec = new SecretKeySpec(tmp.getEncoded(), "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException e) {
LOG.error("Error while encrypting: {}", e);
throw new EncryptDecryptException("Error while initializing encryption mechanism", e);
}
}
public static EncryptDecryptUtils getInstance() {
if (instance == null) {
synchronized (EncryptDecryptUtils .class) {
if (instance == null) {
instance = new EncryptDecryptUtils();
}
}
}
return instance;
}
public String encrypt(String stringToEncrypt){
try {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IV_PARAMETER_SPEC);
return Base64.getEncoder().encodeToString(cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8)));
} catch (BadPaddingException | InvalidKeyException | IllegalBlockSizeException
| InvalidAlgorithmParameterException e) {
LOG.error("Error while encrypting: {}", stringToEncrypt, e);
throw new EncryptDecryptException("Error while encrypting sensitive data", e);
}
}
public String decrypt(String stringToDecrypt){
try {
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IV_PARAMETER_SPEC);
return new String(cipher.doFinal(Base64.getDecoder().decode(stringToDecrypt)), StandardCharsets.UTF_8);
} catch (BadPaddingException | InvalidKeyException | IllegalBlockSizeException
| InvalidAlgorithmParameterException e) {
LOG.error("Error while decrypting: {}", stringToDecrypt, e);
throw new EncryptDecryptException("Error while decrypting sensitive data", e);
}
}
The whole code flow works perfectly fine. But every now and then while decrypting I see this exception being thrown
Caused by: javax.crypto.BadPaddingException: Invalid PKCS#5 padding length: <somenumber>
at iaik.security.cipher.f.b(Unknown Source)
at iaik.security.cipher.a.a(Unknown Source)
at iaik.security.cipher.a.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Cipher.java:2164)
at com.bmw.scmaer.scenario.util.EncryptDecryptUtils.decrypt(EncryptDecryptUtils.java:xxx)
If I rerun the same flow again. It works. It is occuring intermittently. So I am not able to find out the root cause of this.
I am using JPA and eclipselink as ORM.
Any help is much appreciated.

RSA Decryption fails due to input string [duplicate]

This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 7 years ago.
I have the following problem.
I have 2 functions in my code which are intended to encrypt / decrypt simple string.
SO:
I have to pass a string "someString" to the function:
public static String doEncryption(String input) {
try {
if (!RSAService.areKeysPresent()) {
RSAService.generateKey();
}
ObjectInputStream inputStream;
// Encrypt the string using the public key
inputStream = new ObjectInputStream(new FileInputStream(PUBLIC_KEY_FILE));
PublicKey publicKey = (PublicKey) inputStream.readObject();
byte[] cipherText = RSAService.encrypt(input, publicKey);
return cipherText.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "ERROR: Public key file is probably missing";
}
the function doEncryption("someString") returns "[B#61decc8c"
Now I have to embed this string in a url and the server side code should get it from there.
So far it is all good , but when I call the function
public static String doDecryption(String input) {
try {
if (!RSAService.areKeysPresent()) {
RSAService.generateKey();
}
ObjectInputStream inputStream;
// Decrypt the cipher text using the private key.
inputStream = new ObjectInputStream(new FileInputStream(PRIVATE_KEY_FILE));
PrivateKey privateKey = (PrivateKey) inputStream.readObject();
String out = decrypt(input.getBytes(), privateKey);
return out;
} catch (Exception e) {
e.printStackTrace();
}
return "ERROR: Private key file is probably missing or doesn't match the public key";
}
the doDecryption("[B#61decc8c") screams with the following exception:
javax.crypto.BadPaddingException: Data must start with zero
at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:325)
at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:272)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:356)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:382)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at rsaendecryptor.RSAService.decrypt(RSAService.java:132)
at rsaendecryptor.RSAService.doDecryption(RSAService.java:180)
at rsaendecryptor.RSAEnDecrypt.main(RSAEnDecrypt.java:20)
java.lang.NullPointerException
at java.lang.String.<init>(String.java:556)
at rsaendecryptor.RSAService.decrypt(RSAService.java:138)
at rsaendecryptor.RSAService.doDecryption(RSAService.java:180)
at rsaendecryptor.RSAEnDecrypt.main(RSAEnDecrypt.java:20)
Is there any way I can work around this? I have to pass string between the client and the server side because they can be even in different domains. Not to mention that the string will be actually generated from .Net logic and send to Java server side. Encryption to string works fine... What should I do to fix the decryption.
Here is the full class code:
public class RSAService {
/**
* String to hold name of the encryption algorithm.
*/
public static final String ALGORITHM = "RSA";
/**
* String to hold the name of the private key file.
*/
public static final String PRIVATE_KEY_FILE = "private.key";
/**
* String to hold name of the public key file.
*/
public static final String PUBLIC_KEY_FILE = "public.key";
/**
* Generate key which contains a pair of private and public key using 1024
* bytes. Store the set of keys in Prvate.key and Public.key files.
*
*/
public static void generateKey() {
try {
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
keyGen.initialize(1024);
final KeyPair key = keyGen.generateKeyPair();
File privateKeyFile = new File(PRIVATE_KEY_FILE);
File publicKeyFile = new File(PUBLIC_KEY_FILE);
// Create files to store public and private key
privateKeyFile.createNewFile();
if (publicKeyFile.getParentFile() != null) {
publicKeyFile.getParentFile().mkdirs();
}
publicKeyFile.createNewFile();
// Saving the Public key in a file
ObjectOutputStream publicKeyOS = new ObjectOutputStream(
new FileOutputStream(publicKeyFile));
publicKeyOS.writeObject(key.getPublic());
publicKeyOS.close();
// Saving the Private key in a file
ObjectOutputStream privateKeyOS = new ObjectOutputStream(
new FileOutputStream(privateKeyFile));
privateKeyOS.writeObject(key.getPrivate());
privateKeyOS.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* The method checks if the pair of public and private key has been
* generated.
*
* #return flag indicating if the pair of keys were generated.
*/
public static boolean areKeysPresent() {
File privateKey = new File(PRIVATE_KEY_FILE);
File publicKey = new File(PUBLIC_KEY_FILE);
if (privateKey.exists() && publicKey.exists()) {
return true;
}
return false;
}
/**
* Encrypt the plain text using public key.
*
* #param text : original plain text
* #param key :The public key
* #return Encrypted text
* #throws java.lang.Exception
*/
public static byte[] encrypt(String text, PublicKey key) {
byte[] cipherText = null;
try {
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance(ALGORITHM);
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, key);
cipherText = cipher.doFinal(text.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return cipherText;
}
/**
* Decrypt text using private key.
*
* #param text :encrypted text
* #param key :The private key
* #return plain text
* #throws java.lang.Exception
*/
public static String decrypt(byte[] text, PrivateKey key) {
byte[] dectyptedText = null;
try {
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance(ALGORITHM);
// decrypt the text using the private key
cipher.init(Cipher.DECRYPT_MODE, key);
dectyptedText = cipher.doFinal(text);
} catch (Exception ex) {
ex.printStackTrace();
}
return new String(dectyptedText);
}
public static String doEncryption(String input) {
try {
if (!RSAService.areKeysPresent()) {
RSAService.generateKey();
}
ObjectInputStream inputStream;
// Encrypt the string using the public key
inputStream = new ObjectInputStream(new FileInputStream(PUBLIC_KEY_FILE));
PublicKey publicKey = (PublicKey) inputStream.readObject();
byte[] cipherText = RSAService.encrypt(input, publicKey);
return cipherText.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "ERROR: Public key file is probably missing";
}
public static String doDecryption(String input) {
try {
if (!RSAService.areKeysPresent()) {
RSAService.generateKey();
}
ObjectInputStream inputStream;
// Decrypt the cipher text using the private key.
inputStream = new ObjectInputStream(new FileInputStream(PRIVATE_KEY_FILE));
PrivateKey privateKey = (PrivateKey) inputStream.readObject();
String out = decrypt(input.getBytes(), privateKey);
return out;
} catch (Exception e) {
e.printStackTrace();
}
return "ERROR: Private key file is probably missing or doesn't match the public key";
}
}
public static String doEncryption(String input)
Stop right there. String is not a container for binary data, and therefore shouldn't have been used to contain the ciphertext in the first place. It should have been passed around as a byte[].
NB when you get an exception, don't guess at what the condition was and return a string that says what it 'probably' was. It makes debugging a guessing came. Use the message that came with the exception.
Thanks to shikjohari and this article here I was able to fix my code!
in the doEncryption() method I modified the return as follows:
return (Base64.encode(cipherText)).toString();
and in doDecryption() method I modified the return as follows:
String out = decrypt(Base64.decode(input), privateKey);
return out;
You can get the full code from my first post and just edit the returns of the two methods as per this post. Hope this helps.

Categories