package org.identityconnectors.ldap.sync.sunds; import java.io.UnsupportedEncodingException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.identityconnectors.common.Assertions; import org.identityconnectors.framework.common.exceptions.ConnectorException; public class PasswordDecryptor { private static final String ENCRYPTION_ALGORITHM = "DESede/CBC/NoPadding"; // This version magic is used to prefix the password before encryption. // The magic here must match the magic used in wpsync/connector/native/plugin/Util.cpp private static final int KEY_VERSION_MAGIC = 0x132d1403; // Decrypted password format: (4 bytes) magic, (4 bytes) length, password, (4 bytes) magic, padding. private static final int LENGTH_INDEX = 4; private final Cipher cipher; private final int blockSize; public PasswordDecryptor(byte[] desedeKey, byte[] iv) { Assertions.nullCheck(desedeKey, "desedeKey"); Assertions.nullCheck(iv, "iv"); IvParameterSpec ivspec = new IvParameterSpec(iv); // triple-DES key is used SecretKeySpec keyspec = new SecretKeySpec(desedeKey, "DESede"); try { // The cipher algorithm is triple-DES (DESede). // Mode: cipher blocking chaining. // Padding: no padding. cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec); } catch (NoSuchAlgorithmException e) { throw new ConnectorException(e); } catch (NoSuchPaddingException e) { throw new ConnectorException(e); } catch (InvalidAlgorithmParameterException e) { throw new ConnectorException(e); } catch (InvalidKeyException e) { throw new ConnectorException(e); } blockSize = cipher.getBlockSize(); } /** * Decrypts the password value using the configured symmetric key. * * @param encryptedPassword the encrypted password to decrypt. * @return The clear-text password. * @throws ConnectorException if the password value could not be decrypted. */ public String decryptPassword(byte[] encryptedPassword) { byte decryptInput[]; byte decryptedBytes[]; // Pad input if necessary. if (encryptedPassword.length % blockSize != 0) { decryptInput = new byte[((encryptedPassword.length / blockSize) + 1) * blockSize]; System.arraycopy(encryptedPassword, 0, decryptInput, 0, encryptedPassword.length); Arrays.fill(decryptInput, encryptedPassword.length, decryptInput.length, (byte) 0); } else { decryptInput = encryptedPassword; } try { decryptedBytes = cipher.doFinal(decryptInput); } catch (IllegalStateException e) { throw new ConnectorException(e); } catch (IllegalBlockSizeException e) { throw new ConnectorException(e); } catch (BadPaddingException e) { throw new ConnectorException(e); } try { return getDecryptedPassword(decryptedBytes); } catch (UnsupportedEncodingException e) { throw new ConnectorException(e); } } /** * @param password byte array including the decrypted password value. * @return the decrypted password extracted from the raw, unencrypted byte array. * @throws ConnectorException * if the magic cannot be extracted from the byte array for some * reason, or the magic does not match the expected value. * @throws UnsupportedEncodingException * if conversion of the password value to String fails. */ private final String getDecryptedPassword(byte[] password) throws ConnectorException, UnsupportedEncodingException { if (password.length < LENGTH_INDEX + 4) { // Length is 4 bytes. throw new ConnectorException("Invalid decrypted password value: too short"); } int len = getIntValueFromByteArray(password, LENGTH_INDEX); if (len < 0) { throw new ConnectorException("Weird decrypted password value: negative length"); } // Check to see if the computed password length is in the valid range // 12: = 2 * magic length + password length // the raw password field is padded with 0 ... blockSize - 1 bytes. if (len <= password.length - 12 - blockSize || len > password.length - 12) { throw new ConnectorException("Invalid password length"); } checkKeyVersionMagic(password, 8 + len); return new String(password, 8, len, "UTF8"); } private final void checkKeyVersionMagic(byte[] password, int postMagicIndex) throws ConnectorException { if (postMagicIndex < LENGTH_INDEX + 4 || postMagicIndex > password.length - 4) { throw new ConnectorException("Invalid start index for post password magic"); } int premagic = getIntValueFromByteArray(password, 0); int postmagic = getIntValueFromByteArray(password, postMagicIndex); if (premagic != KEY_VERSION_MAGIC || postmagic != KEY_VERSION_MAGIC) { throw new ConnectorException("Key magic mismatch"); } } private int getIntValueFromByteArray(byte[] bytes, int index) { return (getUnsignedByteValueAsInt(bytes[index]) << 24) + (getUnsignedByteValueAsInt(bytes[index + 1]) << 16) + (getUnsignedByteValueAsInt(bytes[index + 2]) << 8) + getUnsignedByteValueAsInt(bytes[index + 3]); } private int getUnsignedByteValueAsInt(byte b) { if (b < 0) { return 256 + b; } else { return b; } } }