Massive Argeo APIs refactoring
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / auth / ident / OpenSslDecryptor.java
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/ident/OpenSslDecryptor.java b/org.argeo.cms/src/org/argeo/cms/auth/ident/OpenSslDecryptor.java
new file mode 100644 (file)
index 0000000..ce7bee9
--- /dev/null
@@ -0,0 +1,162 @@
+package org.argeo.cms.auth.ident;
+
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Base64;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Decrypts OpenSSL encrypted data.
+ * 
+ * From
+ * https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes
+ * 
+ * See also
+ * https://stackoverflow.com/questions/54171959/badpadding-exception-when-trying-to-decrypt-aes-based-encrypted-text/54173509#54173509
+ * for new default message digest (not yet in CentOS 7 as of July 2019)
+ */
+public class OpenSslDecryptor {
+       private static final int INDEX_KEY = 0;
+       private static final int INDEX_IV = 1;
+       private static final int ITERATIONS = 1;
+
+       private static final int SALT_OFFSET = 8;
+       private static final int SALT_SIZE = 8;
+       private static final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
+
+       /** In bits. */
+       private final int keySize;
+
+       private Cipher cipher;
+       private MessageDigest messageDigest;
+
+       public OpenSslDecryptor() {
+               /*
+                * Changed to SHA-256 from OpenSSL v1.1.0 (see
+                * https://stackoverflow.com/questions/39637388/encryption-decryption-doesnt-
+                * work-well-between-two-different-openssl-versions)
+                */
+               this(128, "MD5");
+       }
+
+       public OpenSslDecryptor(int keySize, String messageDigest) {
+               this.keySize = keySize;
+               try {
+                       this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+                       this.messageDigest = MessageDigest.getInstance(messageDigest);
+               } catch (GeneralSecurityException e) {
+                       throw new IllegalArgumentException("Cannot initialise decryptor", e);
+               }
+       }
+
+       public String decryptAuthd(String dataBase64, String passphrase) {
+               try {
+                       byte[] headerSaltAndCipherText = Base64.getDecoder().decode(dataBase64);
+
+                       boolean withSalt = true;
+                       byte[] salt = withSalt ? Arrays.copyOfRange(headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE)
+                                       : null;
+                       byte[] encrypted = withSalt
+                                       ? Arrays.copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length)
+                                       : headerSaltAndCipherText;
+
+                       final byte[][] keyAndIV = EVP_BytesToKey(keySize / Byte.SIZE, cipher.getBlockSize(), messageDigest, salt,
+                                       passphrase.getBytes(StandardCharsets.US_ASCII), ITERATIONS);
+                       SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
+                       IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
+
+                       cipher.init(Cipher.DECRYPT_MODE, key, iv);
+                       byte[] decrypted = cipher.doFinal(encrypted);
+
+                       String answer = new String(decrypted, StandardCharsets.US_ASCII);
+                       return answer;
+               } catch (BadPaddingException e) {
+                       throw new IllegalStateException("Bad password, algorithm, mode or padding;"
+                                       + " no salt, wrong number of iterations or corrupted ciphertext.", e);
+               } catch (IllegalBlockSizeException e) {
+                       throw new IllegalStateException("Bad algorithm, mode or corrupted (resized) ciphertext.", e);
+               } catch (GeneralSecurityException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       private static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md, byte[] salt, byte[] data,
+                       int count) {
+               byte[][] both = new byte[2][];
+               byte[] key = new byte[key_len];
+               int key_ix = 0;
+               byte[] iv = new byte[iv_len];
+               int iv_ix = 0;
+               both[0] = key;
+               both[1] = iv;
+               byte[] md_buf = null;
+               int nkey = key_len;
+               int niv = iv_len;
+               int i = 0;
+               if (data == null) {
+                       return both;
+               }
+               int addmd = 0;
+               for (;;) {
+                       md.reset();
+                       if (addmd++ > 0) {
+                               md.update(md_buf);
+                       }
+                       md.update(data);
+                       if (null != salt) {
+                               md.update(salt, 0, 8);
+                       }
+                       md_buf = md.digest();
+                       for (i = 1; i < count; i++) {
+                               md.reset();
+                               md.update(md_buf);
+                               md_buf = md.digest();
+                       }
+                       i = 0;
+                       if (nkey > 0) {
+                               for (;;) {
+                                       if (nkey == 0)
+                                               break;
+                                       if (i == md_buf.length)
+                                               break;
+                                       key[key_ix++] = md_buf[i];
+                                       nkey--;
+                                       i++;
+                               }
+                       }
+                       if (niv > 0 && i != md_buf.length) {
+                               for (;;) {
+                                       if (niv == 0)
+                                               break;
+                                       if (i == md_buf.length)
+                                               break;
+                                       iv[iv_ix++] = md_buf[i];
+                                       niv--;
+                                       i++;
+                               }
+                       }
+                       if (nkey == 0 && niv == 0) {
+                               break;
+                       }
+               }
+               for (i = 0; i < md_buf.length; i++) {
+                       md_buf[i] = 0;
+               }
+               return both;
+       }
+
+       public static void main(String[] args) {
+               String dataBase64 = args[0];
+               String passphrase = args[1];
+               OpenSslDecryptor decryptor = new OpenSslDecryptor();
+               System.out.println(decryptor.decryptAuthd(dataBase64, passphrase));
+       }
+
+}
\ No newline at end of file