Achieve OpenSSL compatible encryption with Java / PHP

I tried OpenSSL cli compatible encryption / decryption processing in various languages

In a certain project, I was forced to decrypt the text encrypted using OpenSSL cli with various script languages, and it was a little troublesome in the case of Java and PHP, so I summarized the information.

OpenSSL cli encryption / decryption process

You can easily encrypt text at the password pace by using the enc option with OpenSSL cli, for example, but if you try to decrypt it using Java, it will add and analyze OpenSSL compatible header information. It could not be decrypted as it is because there is no corresponding function.

When encrypting the contents of a text file with OpenSSL cli, you can easily obtain an encrypted character string simply by giving the encryption method and password as options as shown in the example below.

cat plain.txt | openssl enc -e -aes-128-cbc -base64 -k <password>

Normally, if AES is specified as the encryption method, it is necessary to give Salt and IV (Initialization Vector) in addition to the password when encrypting and decrypting, but in OpenSSL this Salt and The IV is automatically generated and embedded in the header of the encrypted data, so the user only needs to be aware of the password. As you can see in the sample script in this article, Perl and Ruby's OpenSSL library has an OpenSSL cli encryption compatible Salt and IV generation mechanism, so it's relatively easy to use OpenSSL cli encrypted text. However, in the case of the OpenSSL library for Java and PHP, since it did not have this OpenSSL cli compatible mechanism, it was necessary to incorporate the Salt and IV generation mechanism on its own.

By the way, when encryption is performed by AES method using the enc option with OpenSSL cli, Salt has a random 8-byte (64-bit) bit string, and IV has 16 bytes (16 bytes) generated by the following method. A bit string of 128 bits) is used.

IV generation formula Key = password + Salt MD5 IV = key + password + Salt MD5

These are added as header information to the beginning of the encrypted data and returned as encrypted data.

String "Salted__" (8 bytes) + Salt (8 bytes) + IV (16 bytes) + [encrypted data]

When decrypting, if you confirm that the beginning of the encrypted data is "Salted__", use the following 8 bytes of data as Salt, and from the password, Salt as in the case of encryption Generates an IV and decrypts the body of the encrypted data that follows Salt.

For Java

The following sample code is written by incorporating this. The getKeyAndGenerateIv in this code is used to generate Salt and IV in an OpenSSL Cli compatible way.

EncryptDecryptText.java


import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import java.security.spec.KeySpec;
import java.security.SecureRandom;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Arrays;
import java.io.BufferedReader;
import java.io.InputStreamReader;

class EncryptDecryptText {

    public static boolean getKeyAndGenerateIv(String password, byte[] salt, byte[] key_bytes, byte[] iv_bytes) {
        try {
            byte[] password_bytes = password.getBytes(StandardCharsets.UTF_8);
            int length = password_bytes.length + salt.length;
            ByteBuffer byte_buffer = ByteBuffer.allocate(length);
            byte_buffer.put(password_bytes);
            byte_buffer.put(salt);
            byte_buffer.rewind();
            byte[] byte_array = new byte[length];
            byte_buffer.get(byte_array);
            System.arraycopy(MessageDigest.getInstance("MD5").digest(byte_array), 0, key_bytes, 0, key_bytes.length);
            length = password_bytes.length + salt.length + key_bytes.length;
            byte_buffer = ByteBuffer.allocate(length);
            byte_buffer.put(key_bytes);
            byte_buffer.put(password_bytes);
            byte_buffer.put(salt);
            byte_buffer.rewind();
            byte_array = new byte[length];
            byte_buffer.get(byte_array);
            System.arraycopy(MessageDigest.getInstance("MD5").digest(byte_array), 0, iv_bytes, 0, iv_bytes.length);
        }
        catch ( NoSuchAlgorithmException e ) {
            return false;
        }
        return true;
    }

    public static String encrypt(String plaintext, String password) throws Exception {
        // Generate random salt.
        byte[] random_bytes = new byte[8];
        new SecureRandom().nextBytes(random_bytes);

        byte[] key_bytes = new byte[16];
        byte[] iv_bytes = new byte[16];
        getKeyAndGenerateIv(password, random_bytes, key_bytes, iv_bytes);

        SecretKey secret = new SecretKeySpec(key_bytes, "AES");
        IvParameterSpec ivspec = new IvParameterSpec(iv_bytes);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
        byte[] encrypted_bytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));

        final String header_string = "Salted__";
        byte[] header_bytes = header_string.getBytes(StandardCharsets.UTF_8);
        int length = header_bytes.length + random_bytes.length + encrypted_bytes.length;
        ByteBuffer byte_buffer = ByteBuffer.allocate(length);
        byte_buffer.put(header_bytes);
        byte_buffer.put(random_bytes);
        byte_buffer.put(encrypted_bytes);
        byte_buffer.rewind();
        byte[] byte_array = new byte[length];
        byte_buffer.get(byte_array);

        return new String(Base64.getEncoder().encodeToString(byte_array));
    }

    public static String decrypt(String payload, String password) throws Exception {
        byte[] payload_bytes = Base64.getDecoder().decode(payload.getBytes(StandardCharsets.UTF_8));
        byte[] header_bytes = new byte[8];
        byte[] salt_bytes = new byte[8];
        int length = payload_bytes.length;
        ByteBuffer byte_buffer = ByteBuffer.allocate(length);
        byte_buffer.put(payload_bytes);
        byte_buffer.rewind();
        byte_buffer.get(header_bytes);
        byte_buffer.get(salt_bytes);
        length = payload_bytes.length - header_bytes.length - salt_bytes.length;
        byte[] data_bytes = new byte[length];
        byte_buffer.get(data_bytes);

        byte[] key_byte = new byte[16];
        byte[] iv_bytes = new byte[16];
        getKeyAndGenerateIv(password, salt_bytes, key_byte, iv_bytes);

        SecretKey secret = new SecretKeySpec(key_byte, "AES");
        IvParameterSpec ivspec = new IvParameterSpec(iv_bytes);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
        byte[] decrypted = cipher.doFinal(data_bytes);

        return new String(decrypted);
    }

    public static void main(String[] args) throws Exception {
        //Read the data / password to be encrypted
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.print("Plain text: ");
        System.out.flush();
        String plain_text = br.readLine();

        System.out.print("Password: ");
        System.out.flush();
        String password = br.readLine();

        //Encryption process
        String encrypted = EncryptorDecryptor.encrypt(plain_text, password);
        System.out.print("encrypted:" + encrypted);
        System.out.println();

        //Decryption process
        String decrypted = EncryptorDecryptor.decrypt(encrypted, password);
        System.out.print("decrypted:" + decrypted);
        System.out.println();
    }
}

For PHP

python


<?php

function encrypt($plain_text, $password) {
    //Generate a random 8-byte Salt
    $random_salt = openssl_random_pseudo_bytes(8);

    //Generate Key and IV from password and Salt
    $key_data = $password.$random_salt;
    $raw_key = md5($key_data, true);

    $iv_data = $raw_key.$password.$random_salt;
    $iv = md5($iv_data, true);

    //encryption
    $encrypted = openssl_encrypt($plain_text, 'aes-128-cbc', $raw_key, OPENSSL_RAW_DATA, $iv);
    return ( base64_encode("Salted__".$random_salt.$encrypted) );
}

function decrypt($encrypted_text, $password) {
    //Decryption
    $payload_text = base64_decode($encrypted_text);
    $header = substr($payload_text, 0, 7);
    $salt = substr($payload_text, 8, 8);
    $data = substr($payload_text, 16);
    //Generate Key and IV from password and Salt
    $key_data = $password.$salt;
    $raw_key = md5($key_data, true);
    $iv_data = $raw_key.$password.$salt;
    $iv = md5($iv_data, true);
    $decrypted_text = openssl_decrypt($data, 'aes-128-cbc', $raw_key, OPENSSL_RAW_DATA, $iv);
    return ( $decrypted_text );
}

// Usage:
$str = file_get_contents('php://stdin');
print "Plain text: " . $str . "\n";
$password = $argv[1];
print "Password: " . $password . "\n";

//Encryption process
$encrypted = encrypt($str, $password);
print "encrypted:" . $encrypted . "\n";

//Decryption process
$decrypted = decrypt($encrypted, $password);
print "decrypted:" . $decrypted . "\n";

For Perl

python


#!/usr/bin/perl

use MIME::Base64;
use strict;
use Crypt::CBC;
use Config;

sub encrypt {
  my ($plain_text, $passphrase) = @_;
  my $pbe = Crypt::CBC->new(
     -key => $passphrase,
     -cipher  => 'Crypt::Rijndael',
     -keysize => 128/8,
  );
  my $cipher_text = $pbe->encrypt($plain_text);
  my $encrypted_text = encode_base64($cipher_text);
  return $encrypted_text;
}

sub decrypt {
  my ($encrypted_text, $passphrase) = @_;
  my $cipher_text = decode_base64($encrypted_text);
  my $pbe = Crypt::CBC->new(
     -key => $passphrase,
     -cipher => 'Crypt::Rijndael',
     -keysize => 128/8,
  );
  my $plain_text = $pbe->decrypt($cipher_text);
  return $plain_text;
}

# Usage:
#Encryption process
my $str = (scalar <>);
print "Plain text: " . $str . "\n";
my $password = $ARGV[0];
print "Password: " . $password . "\n";

#Encryption process
my $encrypted = encrypt($str, $password);
print "encrypted:" . $encrypted . "\n";

#Decryption process
my $decrypted = decrypt($encrypted, $password);
print "decrypted:" . $decrypted . "\n";

For Ruby

python


#!/usr/bin/ruby

require "openssl"
require "base64"

def encrypt(plain_text, password)
  salt = OpenSSL::Random.random_bytes(8)
  cipher = OpenSSL::Cipher.new("aes-128-cbc")
  cipher.encrypt()
  cipher.pkcs5_keyivgen(
     password,
     salt,
     1
  )
  #In the case of Ruby, you have to add the header information to the character string by yourself.
  encrypted_text = "Salted__" + salt + cipher.update(plain_text) + cipher.final
  return Base64.encode64(encrypted_text)
end

def decrypt(encrypted_text, password)
  decoded_str = Base64.decode64(encrypted_text)
  #In the case of Ruby, you have to decompose the header information yourself.
  @cipher_text = decoded_str.unpack("a8a8a*")
  cipher = OpenSSL::Cipher.new("aes-128-cbc")
  cipher.pkcs5_keyivgen(
     password,
     @cipher_text[1],
     1
  )
  cipher.decrypt()
  decrypted_text = cipher.update(@cipher_text[2]) + cipher.final
  return decrypted_text
end

# Usage:
str = gets
print "Plain text: " + str + "\n";
password = ARGV[0]
print "Password: " + password + "\n";

#Encryption process
encrypted = encrypt(str, password);
print "encrypted:" + encrypted + "\n";

#Decryption process
decrypted = decrypt(encrypted, password);
print "decrypted:" + decrypted + "\n";

For shell scripts (bash)

python


#!/bin/bash

function encrypt() {
  plain_text=$1
  password=$2
  encrypted_text=`echo -n "$plain_text" | openssl enc -e -aes-128-cbc -base64 -k "$password"`
  echo $encrypted_text
}

function decrypt() {
  encrypted_text=$1
  password=$2
  plain_text=`echo "$encrypted_text" | openssl enc -d -aes-128-cbc -base64 -k "$password"`
  echo $plain_text
}

# Useage:
str="$(cat -)"
echo "Plain text: $str"
password=$1
echo "Password: $password"

#Encryption process
encrypted=`encrypt "$str" "$password"`
echo "encrypted:$encrypted"

#Decryption process
decrypted=`decrypt "$encrypted" "$password"`
echo "decrypted:$decrypted"

Recommended Posts

Achieve OpenSSL compatible encryption with Java / PHP
KMS) Envelope encryption with openssl and java decryption
RSA encryption / decryption with java 8
Make SpringBoot1.5 + Gradle4.4 + Java8 + Docker environment compatible with Java11
Encrypt / decrypt with AES256 in PHP and Java
Make Calendar gadgets made with JavaFX compatible with Java SE 9
[Java] How to encrypt with AES encryption with standard library
Install java with Homebrew
Change seats with java
Comfortable download with JAVA
Switch java with direnv
Download Java with Ansible
Php settings with Docker
Let's scrape with Java! !!
Build Java with Wercker
Disposable PHP with Docker
Endian conversion with JAVA
First year Java developers on udemy get started with PHP
Achieve Mixin-like implementation inheritance: Ruby module, Java interface, PHP trait