1 package org
.argeo
.cms
.auth
.ident
;
3 import java
.nio
.charset
.StandardCharsets
;
4 import java
.security
.GeneralSecurityException
;
5 import java
.security
.MessageDigest
;
6 import java
.util
.Arrays
;
7 import java
.util
.Base64
;
9 import javax
.crypto
.BadPaddingException
;
10 import javax
.crypto
.Cipher
;
11 import javax
.crypto
.IllegalBlockSizeException
;
12 import javax
.crypto
.spec
.IvParameterSpec
;
13 import javax
.crypto
.spec
.SecretKeySpec
;
16 * Decrypts OpenSSL encrypted data.
19 * https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes
22 * https://stackoverflow.com/questions/54171959/badpadding-exception-when-trying-to-decrypt-aes-based-encrypted-text/54173509#54173509
23 * for new default message digest (not yet in CentOS 7 as of July 2019)
25 public class OpenSslDecryptor
{
26 private static final int INDEX_KEY
= 0;
27 private static final int INDEX_IV
= 1;
28 private static final int ITERATIONS
= 1;
30 private static final int SALT_OFFSET
= 8;
31 private static final int SALT_SIZE
= 8;
32 private static final int CIPHERTEXT_OFFSET
= SALT_OFFSET
+ SALT_SIZE
;
35 private final int keySize
;
37 private Cipher cipher
;
38 private MessageDigest messageDigest
;
40 public OpenSslDecryptor() {
42 * Changed to SHA-256 from OpenSSL v1.1.0 (see
43 * https://stackoverflow.com/questions/39637388/encryption-decryption-doesnt-
44 * work-well-between-two-different-openssl-versions)
49 public OpenSslDecryptor(int keySize
, String messageDigest
) {
50 this.keySize
= keySize
;
52 this.cipher
= Cipher
.getInstance("AES/CBC/PKCS5Padding");
53 this.messageDigest
= MessageDigest
.getInstance(messageDigest
);
54 } catch (GeneralSecurityException e
) {
55 throw new IllegalArgumentException("Cannot initialise decryptor", e
);
59 public String
decryptAuthd(String dataBase64
, String passphrase
) {
61 byte[] headerSaltAndCipherText
= Base64
.getDecoder().decode(dataBase64
);
63 boolean withSalt
= true;
64 byte[] salt
= withSalt ? Arrays
.copyOfRange(headerSaltAndCipherText
, SALT_OFFSET
, SALT_OFFSET
+ SALT_SIZE
)
66 byte[] encrypted
= withSalt
67 ? Arrays
.copyOfRange(headerSaltAndCipherText
, CIPHERTEXT_OFFSET
, headerSaltAndCipherText
.length
)
68 : headerSaltAndCipherText
;
70 final byte[][] keyAndIV
= EVP_BytesToKey(keySize
/ Byte
.SIZE
, cipher
.getBlockSize(), messageDigest
, salt
,
71 passphrase
.getBytes(StandardCharsets
.US_ASCII
), ITERATIONS
);
72 SecretKeySpec key
= new SecretKeySpec(keyAndIV
[INDEX_KEY
], "AES");
73 IvParameterSpec iv
= new IvParameterSpec(keyAndIV
[INDEX_IV
]);
75 cipher
.init(Cipher
.DECRYPT_MODE
, key
, iv
);
76 byte[] decrypted
= cipher
.doFinal(encrypted
);
78 String answer
= new String(decrypted
, StandardCharsets
.US_ASCII
);
80 } catch (BadPaddingException e
) {
81 throw new IllegalStateException("Bad password, algorithm, mode or padding;"
82 + " no salt, wrong number of iterations or corrupted ciphertext.", e
);
83 } catch (IllegalBlockSizeException e
) {
84 throw new IllegalStateException("Bad algorithm, mode or corrupted (resized) ciphertext.", e
);
85 } catch (GeneralSecurityException e
) {
86 throw new IllegalStateException(e
);
90 private static byte[][] EVP_BytesToKey(int key_len
, int iv_len
, MessageDigest md
, byte[] salt
, byte[] data
,
92 byte[][] both
= new byte[2][];
93 byte[] key
= new byte[key_len
];
95 byte[] iv
= new byte[iv_len
];
114 md
.update(salt
, 0, 8);
116 md_buf
= md
.digest();
117 for (i
= 1; i
< count
; i
++) {
120 md_buf
= md
.digest();
127 if (i
== md_buf
.length
)
129 key
[key_ix
++] = md_buf
[i
];
134 if (niv
> 0 && i
!= md_buf
.length
) {
138 if (i
== md_buf
.length
)
140 iv
[iv_ix
++] = md_buf
[i
];
145 if (nkey
== 0 && niv
== 0) {
149 for (i
= 0; i
< md_buf
.length
; i
++) {
155 public static void main(String
[] args
) {
156 String dataBase64
= args
[0];
157 String passphrase
= args
[1];
158 OpenSslDecryptor decryptor
= new OpenSslDecryptor();
159 System
.out
.println(decryptor
.decryptAuthd(dataBase64
, passphrase
));