I am trying to replicate the following PHP class in Java to decrypt a string that was encrypted using openssl_encrypt in PHP.
class Encryption {
/* Encryption method */
protected $method = 'aes-128-ctr';
/* Encryption key */
private $key;
/* Property constructor */
public function __construct($key = FALSE, $method = FALSE){
/* Check if custom key provided */
if(!$key) {
/* Set default encryption if none provided */
$key = '1234567891234567';
}
/* Check for control characters in key */
if(ctype_print($key)) {
/* Convert ASCII keys to binary format */
$this->key = openssl_digest($key, 'SHA256', true);
} else {
$this->key = $key;
}
$this->key = $key;
/* Check for custom method */
if($method) {
/* If it is a valid openssl cipher method, use it */
if(in_array(strtolower($method), openssl_get_cipher_methods())) {
$this->method = $method;
/* If it is not, unrecognised method */
} else {
die(__METHOD__ . ": unrecognised cipher method: {$method}");
}
}
}
/* Get iv bytes length */
protected function iv_bytes(){
/* Get length of encryption cipher initialisation vector */
return openssl_cipher_iv_length($this->method);
}
/* Encryption method */
public function encrypt($data) {
/* Get initialisation vector binary */
$iv = openssl_random_pseudo_bytes($this->iv_bytes());
/* Return IV hex & encryption string */
return bin2hex($iv) . openssl_encrypt($data, $this->method, $this->key, OPENSSL_ZERO_PADDING, $iv);
}
/* Decrypt encrypted string */
public function decrypt($data){
/* Get IV string length */
$iv_strlen = 2 * $this->iv_bytes();
/* Parse out the encryption string and unpack the IV and encrypted string. $regs is passed by reference in preg_match() */
if(preg_match("/^(.{" . $iv_strlen . "})(.+)$/", $data, $regs)) {
list(, $iv, $crypted_string) = $regs;
print_R($regs);
/* If there are character representing a hex and the IV string length is divisible by 2 */
if(ctype_xdigit($iv) && (strlen($iv) % 2 == 0)) {
/* Decrypt the unpacked encrypted string */
return openssl_decrypt($crypted_string, $this->method, $this->key, OPENSSL_ZERO_PADDING, hex2bin($iv));
}
}
return FALSE; // failed to decrypt
}
}
Here is my current sandbox class for the decryptor in Java
public class PHPDecryptor {
private static String removeIVHash(String data) {
/* IV string length is 128 bit (16 bytes) * 2 */
int iv_strlen = 2 * 16;
/* Remove IV hash */
String encrypted_string = data.substring(iv_strlen);
return encrypted_string;
}
public static String decrypt(String data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String encrypted_string = removeIVHash(data);
/* Get bytes for string and key */
byte[] text_to_decrypt = encrypted_string.getBytes(StandardCharsets.UTF_8);
String key_string = "1234567891234567";
byte[] key = key_string.getBytes(StandardCharsets.UTF_8);
/* Get secret key and iv */
SecretKeySpec secret_key = new SecretKeySpec(key, "AES");
IvParameterSpec iv = new IvParameterSpec(new byte[16]);
/* Init cipher */
Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret_key, iv);
/* Decrypt and cast to string */
byte[] decrypted = cipher.doFinal(text_to_decrypt);
String result = new String(decrypted, StandardCharsets.UTF_8);
return result;
}
public static void main(String[] arg) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String text_to_decrypt = "203790144a345320d98fb773795d518e/ioQTApeVMV/4g=="; // Encrypted by PHP
System.out.println(decrypt(text_to_decrypt));
}
}
However, I am not getting an accurate decryption - in fact the return result from the Java class is utter nonsense with invalid UTF-8 characters.
Are there any obvious errors here?
It seems like you're not doing anything with the IV, just removing it.
Create an instance of IVParameterSpec from the IV that you removed.
public static IvParameterSpec getIv(String iv) {
return new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
}
You can retrieve the IV from the string better if you use some delimiter (I like to use ":") to separate the iv and the rest of the ciphertext, that way you don't need to worry about the hexed IV length and retrieve it like this:
private static String decodeIvString(String data) {
String[] parts = data.split(":");
String iv = decodeHex(parts[0]);
return iv;
}
Obviously you'll have to add the delimiter in the encrypt function in php.
You can then pass the IV back to the getIv() function.
/* Encryption method */
public function encrypt($data) {
/* Get initialisation vector binary */
$iv = openssl_random_pseudo_bytes($this->iv_bytes());
/* Return IV hex & encryption string */
return bin2hex($iv) . ":" . openssl_encrypt($data, $this->method, $this->key, OPENSSL_ZERO_PADDING, $iv);
}
And as the comments above mentioned, you should replace PKCS5Padding with NoPadding
Here's a utility piece of code that I use to decrypt:
public static byte[] decryptSymmetric(Algorithm algorithm, Key key, IvParameterSpec iv, byte[] data) throws CryptoFailure {
try {
Security.addProvider(new BouncyCastleProvider());
Cipher aes = Cipher.getInstance(algorithm.getMode(), BouncyCastleProvider.PROVIDER_NAME);
aes.init(Cipher.DECRYPT_MODE, key, iv);
return aes.doFinal(data);
} catch (BadPaddingException | IllegalBlockSizeException e) {
throw new CryptoFailure(e);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new CryptoError(e);
}
}
Related
I am using third party application which is in PHP and mine is in java, for which I need to send a few data in an encrypted format. They have shared the secret key and IV. But I am not able to get the same output as they are getting while encryption. Although whatever I am encrypting in java, I am able to get the same while decrypting in java. Please help me out with this.
public static String secret_key = "abc";
public static String secret_iv = "abc#243";
public static String strKeyString = toHexString(getSHA(secret_key));
public static String ivString = toHexString(getSHA(secret_iv));
public static String strKey= strKeyString.substring(0, 16);
public static String strIv = ivString.substring(0, 16);
public static byte[] getSHA(String input)
{
// Static getInstance method is called with hashing SHA
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
// digest() method called
// to calculate message digest of an input
// and return array of byte
return md.digest(input.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static String toHexString(byte[] hash)
{
// Convert byte array into signum representation
BigInteger number = new BigInteger(1, hash);
// Convert message digest into hex value
StringBuilder hexString = new StringBuilder(number.toString(16));
// Pad with leading zeros
while (hexString.length() < 32)
{
hexString.insert(0, '0');
}
return hexString.toString();
}
public static String openssl_encrypt(String data) throws Exception {
Cipher ciper = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(strKey.getBytes(), "AES");
IvParameterSpec iv = new IvParameterSpec(strIv.getBytes(), 0, ciper.getBlockSize());
// Encrypt
ciper.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encryptedCiperBytes = ciper.doFinal(data.getBytes());
System.out.println("Ciper encrypted : " + Base64.encodeBase64String(encryptedCiperBytes));
//Decrypt
ciper.init(Cipher.DECRYPT_MODE, key, iv);
byte[] output = ciper.doFinal(Base64.decodeBase64(Base64.encodeBase64String(encryptedCiperBytes)));
System.out.println("Ciper decrypted : " +new String(output).trim());
return Base64.encodeBase64String(encryptedCiperBytes);
}
I am trying to change JAVA Encryption to PHP and produce exactly the same result.
I have the following guidelines.
AES ‐ CBC with PKCS5 Padding Symmetric Encryption Scheme:
Encryption key size would be of 128 bit size.
Initialization Vector (IV) :
-New Random IV would be used in each request.
-In a single web‐service request, same IV would be used while encrypting all the encrypted fields.
-This IV would be passed in SOAP Header with name as “IV”. IV value would be Base64 encoded.
I have tried this https://gist.github.com/thomasdarimont/fae409eaae2abcf83bd6633b961e7f00
public class AESEncryptionUtil {
public static final String CLASS_NAME = AESEncryptionUtil.class.getName(); private static final int KEY_SIZE = 16;
private static final String ALGORITHM_AES = "AES";
public final static String ALGORITHM_AES_CBC = "AES/CBC/PKCS5Padding";
private static Key generateKey(String keyValue) throws Exception { Key key = null ;
if (keyValue!=null && keyValue.length()==KEY_SIZE){
byte[] byteKey = keyValue.substring(0, KEY_SIZE).getBytes("UTF-8");
key = new SecretKeySpec(byteKey, ALGORITHM_AES);
}else{
System.out.println("Not generating the Key!! "+keyValue); }
return key;
}
/**
* Return Base64 Encoded value of IV *
* #param keyValue * #return
* #throws Exception */
public static String generateIV(String keyValue) throws Exception { String iv = null ;
Key key = generateKey(keyValue); if (key!=null){
Cipher cipher = Cipher.getInstance(ALGORITHM_AES_CBC); cipher.init(Cipher.ENCRYPT_MODE, key); AlgorithmParameters params = cipher.getParameters();
iv = new BASE64Encoder().encode(params.getParameterSpec(IvParameterSpec.class).getIV());
}else{
System.out.println("No IV generated ...");
}
return iv; }
/**
* Method to perform encryption of given data with AES Algorithm / Key and IV. * #param encKey -
*Encryption Key value * #param plainVal -
*Value to be encrypted * #return - encrypted String Value * #throws Exception
*/
public static String encrypt(String encKey, String plainVal, String currentIV) throws Exception {
String encryptedText = null ; Key key = generateKey(encKey);
if (key!=null && currentIV!=null && plainVal!=null){
Cipher c = Cipher.getInstance(ALGORITHM_AES_CBC);
c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new BASE64Decoder().decodeBuffer(currentIV)));
byte[] encValue = c.doFinal(plainVal.getBytes()); encryptedText= new BASE64Encoder().encode(encValue);
}else{
System.out.println("Invalid input passed to encrypt !! keyValue="+encKey+", IV="+currentIV+", valueToEnc="+plainVal);
}
return encryptedText; }
}
I managed to get something to work
<?php
$string = "online1234";
$key = "haskingvista127$";
$iv = base64_encode(openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-128-cbc')));
$encodedEncryptedData = base64_encode(openssl_encrypt($string, "AES-128-CBC", $key, OPENSSL_RAW_DATA, base64_decode($iv)));
$decryptedData = openssl_decrypt(base64_decode($encodedEncryptedData), "AES-128-CBC", $key, OPENSSL_RAW_DATA, base64_decode($iv));
?>
Hope this might help someone else
Im trying to cycle through a byte array and decode it into a string, using RSA encryption, the encryption works with out an array but I am trying to make it usable for longer data, by encrypting each word of the string, but when doing this i get the error required String[] found String Java.
// Decrypt the cipher text using the private key.
inputStream = new ObjectInputStream(new FileInputStream(PRIVATE_KEY_FILE));
final PrivateKey privateKey = (PrivateKey) inputStream.readObject();
String[][] decryptedText = new String[cipherText.length][];
for (int i = 0; i < cipherText.length; i++) {
**ERROR ON THIS LINE - required String[] found String Java**
decryptedText[i] = decrypt(cipherText[i], privateKey);
}
the decrypt method
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 (InvalidKeyException | NoSuchAlgorithmException | BadPaddingException |IllegalBlockSizeException | NoSuchPaddingException ex) {
}
return new String(dectyptedText);
}
Encrypt Method
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 (InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException e) {
}
return cipherText;
}
Problem is your decryptedText is a two dimensional array
String[][] decryptedText = new String[cipherText.length][];
so this line
decryptedText[i] = decrypt(cipherText[i], privateKey);
must put an array to the decryptedText. You can change declaration of decryptedText to fix this
String[] decryptedText = new String[cipherText.length];
Hope this helps.
decryptedText is declared as
String[][] decryptedText
It's thus an array of arrays of Strings.
Thus, decryptedText[i] is the ith element in this array of arrays. It's thus an array of Strings. And you try to initialize it with the returned value of decrypt(), which doesn't return an array of Strings, but a String. Hence the error.
I don't understand why you have a 2D array, and not simply an array of Strings.
I have the following class I use to store encrypted preferences to use with my application (using interface with 3rd part site which does not support OAuth)...
public class CryptoTranslator {
private static SecretKey SEC_KEY;
/**
* #return the sEC_KEY
*/
public static SecretKey getSEC_KEY() {
return SEC_KEY;
}
public static String getSEC_KEY_String(){
return Base64.encodeToString(SEC_KEY.getEncoded(), Base64.DEFAULT);
}
/**
* #param sEC_KEY the sEC_KEY to set
*/
public static void setSEC_KEY(SecretKey sEC_KEY) {
SEC_KEY = sEC_KEY;
}
public static void setSEC_KEY_STRING(String sEC_KEY){
byte[] key = Base64.decode(sEC_KEY, Base64.DEFAULT);
SEC_KEY = new SecretKeySpec(key, 0, key.length, "AES");
}
public static void generateKey() throws NoSuchAlgorithmException {
// Generate a 256-bit key
final int outputKeyLength = 256;
SecureRandom secureRandom = new SecureRandom();
// Do *not* seed secureRandom! Automatically seeded from system entropy.
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(outputKeyLength, secureRandom);
SecretKey key = keyGenerator.generateKey();
SEC_KEY = key;
}
private static byte[] getRawKey() throws Exception {
if (SEC_KEY == null){
generateKey();
}
byte[] raw = SEC_KEY.getEncoded();
return raw;
}
/**
*
*
* #param clear clear text string
* #param mode this should either be Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE
* #return
* #throws Exception
*/
private static String translate(String clear, int mode) throws Exception {
if(mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE)
throw new IllegalArgumentException("Encryption invalid. Mode should be either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE");
SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(mode, skeySpec);
byte[] encrypted = cipher.doFinal(clear.getBytes());
return new String(encrypted);
}
public static String encrypt(String clear) throws Exception {
return translate(clear,Cipher.ENCRYPT_MODE);
}
public static String decrypt(String encrypted) throws Exception {
return translate(encrypted,Cipher.DECRYPT_MODE);
}
}
So now I have encrypted and stored the data. Now I want to pull it out...
String secString = settings.getString(SEC_KEY, null);
if (secString == null) {
try {
CryptoTranslator.generateKey();
settings.edit()
.putString(SEC_KEY,
CryptoTranslator.getSEC_KEY_String()).commit();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
CryptoTranslator.setSEC_KEY_STRING(secString);
}
try {
getUserNamePassword();
} catch (Exception ex) {
Log.i("Preferences",
"There was an issue getting username and password");
isStored = CRED_STATUS_DEF;
}
...
private static void getUserNamePassword() throws Exception {
isStored = settings.getBoolean(CRED_STATUS, CRED_STATUS_DEF);
if (isStored) {
if (settings.contains(USERNAME_KEY))
username = settings.getString(USERNAME_KEY, "");
if (settings.contains(PASSWORD_KEY))
password = settings.getString(PASSWORD_KEY, "");
}
isUsernamePasswordValid();
if (isStored) {
String username2 = CryptoTranslator.decrypt(username);
Log.d("Security", "Username encrypted");
String password2 = CryptoTranslator.decrypt(password);
username = username2;
password = password2;
Log.d("Security", "Password encrypted");
}
}
But this gives me the following error....
javax.crypto.IllegalBlockSizeException: last block incomplete in decryption
Can someone see what I am doing wrong?
Update
Ok per the response I went ahead and changed my code to the following...
public static final int IV_LENGTH = 16;
private static final String RANDOM_ALGORITHM = "SHA1PRNG";
...
private static String translate(String clear, int mode) throws Exception {
if (mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE)
throw new IllegalArgumentException(
"Encryption invalid. Mode should be either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE");
SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(generateIv());
cipher.init(mode, skeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(clear.getBytes());
return new String(encrypted);
}
...
private static byte[] generateIv() throws NoSuchAlgorithmException,
NoSuchProviderException {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(iv);
return iv;
}
Now I get...
javax.crypto.BadPaddingException: pad block corrupted
To try and use hex changed to...
private static byte[] translate(byte[] val, int mode) throws Exception {
if (mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE)
throw new IllegalArgumentException(
"Encryption invalid. Mode should be either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE");
SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(generateIv());
cipher.init(mode, skeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(val);
return encrypted;
}
This seems to almost work (I am getting the .com back) but the chars are still pretty jumbled.
public static String encrypt(String clear) throws Exception {
byte[] test = translate(clear.getBytes(), Cipher.ENCRYPT_MODE);
return new String(Hex.encodeHex(test));
}
public static String decrypt(String encrypted) throws Exception {
return new String(translate(Hex.decodeHex(encrypted.toCharArray()), Cipher.DECRYPT_MODE));
}
*The converting to Hex and back is screwed up here.
So there are a couple of issues with your code.
First is the output of an AES cipher is not character data, you are mangling your ciphertext by trying to put it in a String. When you try to decrypt your mangled ciphertext it is now the wrong length. You need to Base64 or Hex encode the ciphertext if you want to store it in a String and then decode it back in to a byte[] before decrypting it.
Second, when you specify just AES for your cipher spec Java expands that to AES/ECB/PKCS5Padding. ECB is an insecure cipher mode if you intend to encrypt more than 1 block of data (16 bytes for AES). I recommend you switch to a different spec AES/CBC/PKCS5Padding should be acceptable. Using a mode other than ECB will require an Initialization Vector (IV). The IV should be randomly generated but does not need to be secret, so you can store as plaintext with your ciphertext as you'll need it to decrypt as well. The initialization vector needs to be one block in length (16 bytes for AES). Do not reuse the same IV with the same AES key ever, generate a new IV for each encryption being done.
Finally, if your going to store IV + ciphertext in a third party service I recommend you add a MAC (such as HMACSHA1). A MAC will ensure the integrity of your IV + ciphertext before you attempt to decrypt it. A MAC will require a secret key as well, and you should not use the same key you generated for the cipher itself. You can prepend the generated MAC to your IV + ciphertext, so now you are storing MAC + IV + ciphertext.
Android AES client side + PHP AES server side it will throw this error :)
The solution is:
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Please search over the internet for the full source code. I am under NDA and to lazzy to make anonymous my whole code regarding this part, but I am sure you will find it.
I'm having an issue trying to encrypt and decrypt a string using BouncyCastle.
I'm following an example at http://www.aviransplace.com/2004/10/12/using-rsa-encryption-with-java/ and my code looks like:
public class Cryptotests {
public static final String ALGORITHM = "RSA";
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
try {
init();
KeyPair kp = generateKey();
byte[] enc = encrypt("The Fat Cat Jumped Over the Bat".getBytes("UTF8"), kp.getPublic());
byte[] dec = decrypt(enc, kp.getPrivate());
} catch (Exception ex) {
Logger.getLogger(Cryptotests.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void init() {
Security.addProvider(new BouncyCastleProvider());
}
public static KeyPair generateKey() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
keyGen.initialize(1024);
KeyPair key = keyGen.generateKeyPair();
return key;
}
/**
* Encrypt a text using public key.
*
* #param text The original unencrypted text
* #param key The public key
* #return Encrypted text
* #throws java.lang.Exception
*/
public static byte[] encrypt(byte[] text, PublicKey key) throws Exception {
byte[] cipherText = null;
// get an RSA cipher object and print the provider
Cipher cipher = Cipher.getInstance(
"RSA / ECB / PKCS1Padding");
System.out.println(
"nProvider is:" + cipher.getProvider().getInfo());
// encrypt the plaintext using the public key
cipher.init(Cipher.ENCRYPT_MODE, key);
cipherText = cipher.doFinal(text);
return cipherText;
}
/**
* Decrypt text using private key
*
* #param text The encrypted text
* #param key The private key
* #return The unencrypted text
* #throws java.lang.Exception
*/
public static byte[] decrypt(byte[] text, PrivateKey key) throws Exception {
byte[] dectyptedText = null;
// decrypt the text using the private key
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
dectyptedText = cipher.doFinal(text);
return dectyptedText;
}
}
When I Run this code I end up with an error:
May 21, 2013 10:20:31 AM cryptotests.Cryptotests main
SEVERE: null
java.security.InvalidKeyException: Illegal key size or default parameters
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1011)
at javax.crypto.Cipher.init(Cipher.java:1209)
at javax.crypto.Cipher.init(Cipher.java:1153)
at cryptotests.Cryptotests.encrypt(Cryptotests.java:70)
at cryptotests.Cryptotests.main(Cryptotests.java:34)
I'm really new and quite honestly feeling a bit lost when it comes to cryptography. My goal is to figure this out so that I can create and use a RSA key pair using SHA512 and a 4k length. I'm having a lot of trouble finding clear examples of how to do achieve this.
Need to install the Unlimited Java Cryptography Extension (JCE) Unlimited Strength
http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html