0

I'm attempting to implement Triple DES encryption (DESede/ECB w/ ZeroPadding) in my Java application using Bouncy Castle. I'm running into an issue where the encrypted value that is returned from the implementation doesn't match the expected encrypted value.

if key = "987ff3cc1e3d58d4a698985201d02aed" and unencryptedString = "4548812028759309-0394-123", I expect the resulting encrypted string to equal "6d2db19ab8ff3ae7c59ef7af0813c84eabe7c8e551fedfc94a607a06b239f4ef". My implementation is returning "b8b63dc7c1ec5330b4e19e1210180a1ffa66936298ed8032ea38b7febb9ece98".

What am I overlooking?

Triple DES Implementation using Bouncy Castle

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Security;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;

import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;

public class TripleDESEncryptor {

    private static final String UNICODE_FORMAT = "UTF8";
    private static final String DESEDE_ECB_ENCRYPTION_SCHEME = "DESede/ECB/NoPadding";
    private static final String DESEDE_ENCRYPTION_SCHEME = "DESede";
    private static String BOUNCY_CASTLE_FIPS_PROVIDER = "BCFIPS";
    private final Cipher cipher;
    private final SecretKey key;

    public TripleDESEncryptor(byte[] encryptionKey) {
        try {
            ensureBouncyCastleFIPSProviderIsAvailable();
            cipher = Cipher.getInstance(DESEDE_ECB_ENCRYPTION_SCHEME, BOUNCY_CASTLE_FIPS_PROVIDER);
            key = SecretKeyFactory
                    .getInstance(DESEDE_ENCRYPTION_SCHEME, BOUNCY_CASTLE_FIPS_PROVIDER)
                    .generateSecret(new DESedeKeySpec(encryptionKey));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public String encrypt(String unencryptedString)
            throws InvalidKeyException, InvalidAlgorithmParameterException,
            UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException {
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] paddedPlainText = padWithZeroBytes(unencryptedString.getBytes(UNICODE_FORMAT));
        return Hex.encodeHexString(cipher.doFinal(paddedPlainText));
    }

    private byte[] padWithZeroBytes(byte[] unencryptedString) {
        return Arrays.copyOf(unencryptedString, roundUpToMultipleOf8(unencryptedString.length));
    }

    private int roundUpToMultipleOf8(int x) {
        return ((x + 7) & (-8));
    }

    private void ensureBouncyCastleFIPSProviderIsAvailable() {
        var bcfProvider = Arrays.stream(Security.getProviders())
                .filter(provider -> BOUNCY_CASTLE_FIPS_PROVIDER.equals(provider.getName()))
                .findAny().orElse(null);
        if (bcfProvider == null)
            Security.addProvider(new BouncyCastleFipsProvider());
    }
}

Unit Test

import static org.junit.Assert.assertEquals;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;

import org.junit.Test;

public class TripleDESEncryptorTest {

    @Test
    public void encrypt()
            throws InvalidKeyException, InvalidParameterSpecException, IllegalBlockSizeException,
            BadPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException {
        byte[] key = "987ff3cc1e3d58d4a698985201d02aed".getBytes("UTF8");
        var encryptor = new TripleDESEncryptor(key);
        String cleartext = "4548812028759309-0394-123";
        String encrypted = encryptor.encrypt(cleartext);
        assertEquals("6d2db19ab8ff3ae7c59ef7af0813c84eabe7c8e551fedfc94a607a06b239f4ef", encrypted);
    }

}
Community
  • 1
  • 1
SpeaksBinary
  • 185
  • 1
  • 17
  • Is your key the sequence of bytes containing the characters `'9'`, `'8'`, `'7'`,... `'e'`, `'d'`, which I think is what the line `byte[] key =` is creating, or a sequence of bytes with the hex values `0x98`, `0x7f`,... `0xed` which is what I suspect you want? Perhaps you want something like [this answer](https://stackoverflow.com/a/140861/2096401). – TripeHound Jun 03 '20 at 19:14
  • Your code doesn't process 16, but only 24 bytes keys. All longer keys are truncated, all shorter keys cause an exception. Therefore, the hex encoded 16 bytes key must be extended to a 24 bytes key by appending the first 8 bytes at the end: `987ff3cc1e3d58d4a698985201d02aed987ff3cc1e3d58d4` (s. [2TDEA](https://en.wikipedia.org/wiki/Triple_DES#Keying_options). As @TripeHound already noted, the key must be hex decoded. The _Apache Commons Codec_ (you already use) provides `Hex.decodeHex(...)` for this purpose. Note: ECB is insecure, Zero-Padding unreliable and 3DES slower than AES. – Topaco Jun 03 '20 at 20:01
  • Enough hints in above. One final need-to-know: the Bouncy Castle zero padding implementation *always* pads, while many other libraries do not pad if the size of the plaintext is already a multiple of the block size. You'll have to pad yourself if you don't want to always pad (or you can just remove the final block from the ciphertext if the plaintext is a multiple of the block size) – Maarten Bodewes Jun 03 '20 at 20:09
  • Awesome! Thanks for the info guys! I'm aware of the insecurity of 3DES/ECB. Unfortunately, this encryption algorithm is required by a 3rd part this application integrates with – SpeaksBinary Jun 03 '20 at 20:11

0 Answers0