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
Related
I had issue when converting AES encryption from java to php, it show different value result in php, that should show the exact value from java
Java
public class AesUtil {
private final Cipher cipher;
public final static String passPhrase = "";
public final static String IV = ""; //32bit IV Lenght
public final static String SALT = "";
public final static int KEY_SIZE = 128;
public final static int ITERATION_COUNT = 10000;
public AesUtil() {
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
}
catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw fail(e);
}
}
//Call this function to encrypt some String
public String encrypt(String passPhrase, String plaintext) {
try {
SecretKey key = generateKey(SALT, passPhrase);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, IV, plaintext.getBytes("UTF-8"));
return base64(encrypted);
}
catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
try {
cipher.init(encryptMode, key, new IvParameterSpec(hex(IV)));
return cipher.doFinal(bytes);
}
catch (InvalidKeyException
| InvalidAlgorithmParameterException
| IllegalBlockSizeException
| BadPaddingException e) {
throw fail(e);
}
}
private SecretKey generateKey(String salt, String passphrase) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt), ITERATION_COUNT, KEY_SIZE);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return key;
}
catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw fail(e);
}
}
public static String random(int length) {
byte[] salt = new byte[length];
new SecureRandom().nextBytes(salt);
return hex(salt);
}
public static String base64(byte[] bytes) {
return new String(Base64.encodeBase64(bytes));
}
public static byte[] base64(String str) {
return Base64.decodeBase64(str.getBytes());
}
public static String hex(byte[] bytes) {
return Hex.encodeHexString(bytes);
}
public static byte[] hex(String str) {
try {
return Hex.decodeHex(str.toCharArray());
}
catch (DecoderException e) {
throw new IllegalStateException(e);
}
}
private IllegalStateException fail(Exception e) {
return new IllegalStateException(e);
}
}
this is my code in php version,
<?php
$password = "";
$salt = "";
$iv = ""; //32bit IV Lenght
$length = 16;
$salt = "";
$interation = 10000;
$key1 = mb_convert_encoding($password, "UTF-8");
$bytes = openssl_pbkdf2($key1, $salt, $length, $interation, "sha1");
$bytes2 = hash_pbkdf2("sha1", $password, $salt, $interation, $length, true);
$ciphertext_b64 = base64_encode(openssl_encrypt($plaintext,"aes-128-cbc", $bytes2,OPENSSL_RAW_DATA, $iv));
?>
Please tell me how to solve this problem, if you have any suggestion in different programming language it's no problem, thank you
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.
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
Google have provided the following example code showing how to generate a secure token for their second version of Recaptcha:
public class STokenUtils {
private static final String CIPHER_INSTANCE_NAME = "AES/ECB/PKCS5Padding";
public static final String createSToken(String siteSecret) {
String sessionId = UUID.randomUUID().toString();
String jsonToken = createJsonToken(sessionId);
return encryptAes(jsonToken, siteSecret);
}
private static final String createJsonToken(String sessionId) {
JsonObject obj = new JsonObject();
obj.addProperty("session_id", sessionId);
obj.addProperty("ts_ms", System.currentTimeMillis());
return new Gson().toJson(obj);
}
private static String encryptAes(String input, String siteSecret) {
try {
SecretKeySpec secretKey = getKey(siteSecret);
Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE_NAME);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return BaseEncoding.base64Url().omitPadding().encode(cipher.doFinal(input.getBytes("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String decryptAes(String input, String key) throws Exception {
SecretKeySpec secretKey = getKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE_NAME);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(
BaseEncoding.base64Url().omitPadding().decode(input)), "UTF-8");
}
private static SecretKeySpec getKey(String siteSecret){
try {
byte[] key = siteSecret.getBytes("UTF-8");
key = Arrays.copyOf(MessageDigest.getInstance("SHA").digest(key), 16);
return new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}
The full code can be found at: https://github.com/google/recaptcha-java
I'm wanting to generate this token in Ruby 2.1+ and have got this far but it outputs incorrect data. I'm trying to slowly debug it, but in the meantime I'm wondering if anyone can see any obvious flaws in my process?
stoken_json = hash_to_json({'session_id' => SecureRandom.uuid, 'ts_ms' => Time.now.to_i})
cipher = OpenSSL::Cipher::AES128.new(:ECB)
private_key_digest = Digest::SHA1.hexdigest(private_key)[0...16]
cipher.encrypt
cipher.key = private_key_digest
encrypted_stoken = cipher.update(stoken_json) << cipher.final
encoded_stoken = Base64.urlsafe_encode64(encrypted_stoken).gsub(/\=+\Z/, '')
Turns out I was close. I needed to digest not hexdigest the private key:
private_key_digest = Digest::SHA1.digest(private_key)[0...16]
So the final code is:
stoken_json = hash_to_json({'session_id' => SecureRandom.uuid, 'ts_ms' => (Time.now.to_f * 1000).to_i})
cipher = OpenSSL::Cipher::AES128.new(:ECB)
private_key_digest = Digest::SHA1.digest(private_key)[0...16]
cipher.encrypt
cipher.key = private_key_digest
encrypted_stoken = cipher.update(stoken_json) << cipher.final
encoded_stoken = Base64.urlsafe_encode64(encrypted_stoken).gsub(/\=+\Z/, '')
There didn't seem to be a built-in way to strip the padding from the base64 string, thus the .gsub at the end.
I also needed the timestamp in milliseconds so that part has been modified too.
In the recaptcha gem there is a method hash_to_json that I'm using, otherwise I suspect you'd use the JSON gem.
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.