Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Encryption Diff Between Java and C#

Hi I am trying to figure out how to replicate the encryption of text done in C# but in Java. the part of the code that is still puzzling me and cant seem to find the answer to is this in C#:

PasswordDeriveBytes myPass = new PasswordDeriveBytes(String Password, byte[] Salt);
Trp.Key = myPass.GetBytes(24);
Trp.IV = myPass.GetBytes(8);

Basically what would be the equivalent of this code in Java? UPDATE: Using the provided code for PasswordDeriveBytes (the second snippet) i was able to replicate the C# code perfectly. Thank you Maarten Bodewes.

BASE64Encoder base64 = new BASE64Encoder();
PasswordDeriveBytes i_Pass = new PasswordDeriveBytes(passWord, saltWordAsBytes);
byte[] keyBytes = i_Pass.getBytes(24);
byte[] ivBytes = i_Pass.getBytes(8);
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(keyBytes, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(ivBytes);
c3des.init (Cipher.ENCRYPT_MODE, myKey, ivspec);
encrytpedTextAsByte  = c3des.doFinal(plainTextAsBytes);
encryptedText  = base64.encode(encrytpedTextAsByte);

but can't seem to get it to work across platform. basically the decoding code is set (i cant change in C# 3.5) and i am trying to encode in java so that the C# code can decode it.

Any help would be greatly appreciated.

like image 260
Jay Hysenbegasi Avatar asked Jan 06 '15 03:01

Jay Hysenbegasi


1 Answers

The problem is that PasswordDeriveBytes is only defined for the first 20 bytes - in that case it is PBKDF1 (not 2, as you are currently using in your Java code). Calling getBytes multiple times may also change the result. The algorithm for one or more calls to getBytes or for more than 20 bytes is Microsoft proprietary and doesn't seem to be described anywhere. In Mono it's even described as a non-fix as it may not be secure.

I would strongly suggest to use RFC2898DeriveBytes that does implement PBKDF2. Beware to only use it for ASCII input or it may not be compatible with the Java implementation.

The only other option is to figure out the proprietary extension of Microsoft PasswordDeriveBytes to PBKDF1 (which only defines output up to the hash size of 20 bytes). I've reimplemented the version of Mono below.

Repeated request to Microsoft to update the API description of this function did not produce any results. You may want to read this bug report if your results differ.


This is the proprietary Microsoft extension. Basically it first calculates PBKDF-1 up to but not including the last hash iteration, call this HX. For the first 20 bytes it simply performs another hash, so it is compliant to PBKDF1. The next hashes are over the ASCII representation of a counter starting at 1 (so this is first converted to "1", then to 0x31), followed by the bytes of HX.

What follows is a minimalistic, rather direct conversion from the Mono code:

public class PasswordDeriveBytes {

    private final MessageDigest hash;
    private final byte[] initial;
    private final int iterations;

    private byte[] output;
    private int hashnumber = 0;
    private int position = 0;

    public PasswordDeriveBytes(String password, byte[] salt) {
        try {
            this.hash = MessageDigest.getInstance("SHA-1");
            this.initial = new byte[hash.getDigestLength()];
            this.hash.update(password.getBytes(UTF_8));
            this.hash.update(salt);
            this.hash.digest(this.initial, 0, this.initial.length);
            this.iterations = 100;
        } catch (NoSuchAlgorithmException | DigestException e) {
            throw new IllegalStateException(e);
        }
    }

    public byte[] getBytes(int cb) {
        if (cb < 1)
            throw new IndexOutOfBoundsException("cb");
        byte[] result = new byte[cb];
        int cpos = 0;
        // the initial hash (in reset) + at least one iteration
        int iter = Math.max(1, iterations - 1);
        // start with the PKCS5 key
        if (output == null) {
            // calculate the PKCS5 key
            output = initial;
            // generate new key material
            for (int i = 0; i < iter - 1; i++)
                output = hash.digest(output);
        }
        while (cpos < cb) {
            byte[] output2 = null;
            if (hashnumber == 0) {
                // last iteration on output
                output2 = hash.digest(output);
            } else if (hashnumber < 1000) {
                String n = String.valueOf(hashnumber);
                output2 = new byte[output.length + n.length()];
                for (int j = 0; j < n.length(); j++)
                    output2[j] = (byte) (n.charAt(j));
                System.arraycopy(output, 0, output2, n.length(), output.length);
                // don't update output
                output2 = hash.digest(output2);
            } else {
                throw new SecurityException();
            }
            int rem = output2.length - position;
            int l = Math.min(cb - cpos, rem);
            System.arraycopy(output2, position, result, cpos, l);
            cpos += l;
            position += l;
            while (position >= output2.length) {
                position -= output2.length;
                hashnumber++;
            }
        }
        return result;
    }
}

Or, a bit more optimized and readable, leaving just the output buffer and position to be changed in between calls:

public class PasswordDeriveBytes {

    private final MessageDigest hash;

    private final byte[] firstToLastDigest;
    private final byte[] outputBuffer;

    private int position = 0;

    public PasswordDeriveBytes(String password, byte[] salt) {
        try {
            this.hash = MessageDigest.getInstance("SHA-1");

            this.hash.update(password.getBytes(UTF_8));
            this.hash.update(salt);
            this.firstToLastDigest = this.hash.digest();

            final int iterations = 100;
            for (int i = 1; i < iterations - 1; i++) {
                hash.update(firstToLastDigest);
                hash.digest(firstToLastDigest, 0, firstToLastDigest.length);
            }

            this.outputBuffer = hash.digest(firstToLastDigest);

        } catch (NoSuchAlgorithmException | DigestException e) {
            throw new IllegalStateException("SHA-1 digest should always be available", e);
        }
    }

    public byte[] getBytes(int requested) {
        if (requested < 1) {
            throw new IllegalArgumentException(
                    "You should at least request 1 byte");
        }

        byte[] result = new byte[requested];

        int generated = 0;

        try {
            while (generated < requested) {
                final int outputOffset = position % outputBuffer.length;
                if (outputOffset == 0 && position != 0) {
                    final String counter = String.valueOf(position / outputBuffer.length);
                    hash.update(counter.getBytes(US_ASCII));
                    hash.update(firstToLastDigest);
                    hash.digest(outputBuffer, 0, outputBuffer.length);
                }

                final int left = outputBuffer.length - outputOffset;
                final int required = requested - generated;
                final int copy = Math.min(left, required);

                System.arraycopy(outputBuffer, outputOffset, result, generated, copy);

                generated += copy;
                position += copy;
            }
        } catch (final DigestException e) {
            throw new IllegalStateException(e);
        }
        return result;
    }
}

It's actually not all that bad security-wise as the bytes are separated from each other by the digest. So the key stretching is relatively OK. Note that there were Microsoft PasswordDeriveBytes implementations out there that contained a bug and repeated bytes (see bug report above). This is not reproduced here.

Usage:

private static final String PASSWORD = "46dkaKLKKJLjdkdk;akdjafj";

private static final byte[] SALT = { 0x26, 0x19, (byte) 0x81, 0x4E,
        (byte) 0xA0, 0x6D, (byte) 0x95, 0x34 };

public static void main(String[] args) throws Exception {
    final Cipher desEDE = Cipher.getInstance("DESede/CBC/PKCS5Padding");

    final PasswordDeriveBytes myPass = new PasswordDeriveBytes(PASSWORD, SALT);
    final SecretKeyFactory kf = SecretKeyFactory.getInstance("DESede");
    final byte[] key = myPass.getBytes(192 / Byte.SIZE);
    final SecretKey desEDEKey = kf.generateSecret(new DESedeKeySpec(key));

    final byte[] iv = myPass.getBytes(desEDE.getBlockSize());

    desEDE.init(Cipher.ENCRYPT_MODE, desEDEKey, new IvParameterSpec(iv));

    final byte[] ct = desEDE.doFinal("owlstead".getBytes(US_ASCII));
}

Side notes about the Java implementation:

  • the iteration count is too low, check out what kind of iteration counts are required at the current date
  • the key size is incorrect, you should create keys of 3 * 64 = 192 bits instead of 196 bits
  • 3DES is getting old, use AES instead
like image 63
Maarten Bodewes Avatar answered Oct 13 '22 20:10

Maarten Bodewes