Skip to Main Content

Java Security

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

Simulate Oracle DES Password Hashing in Java

843811Nov 17 2009 — edited Nov 18 2009
Hi all,

I'm trying to simulate Oracle's password Hashing in Java., because I want to change the authentification-mechanism of an application (currently via Oracle) to an application-build-in authentification.

I used this website as guideline: http://www.sans.org/reading_room/special/?id=oracle_pass&ref=911

But the output is not what I exspected. What's wrong? Any idea?

Expected: 342C0CF1DDCAD9F3
Got:
9gQc62TlB/zI3pN6NNr2CVzALS8g2CNI8JSvMjmoh234MlGJ8FgDIroun4MOgTCvQbDYN1Ww2dRV
cN3vHF1tfbQMJuFZMqBca1N5GLiAbvw=

This is my code:
import java.math.BigInteger;
import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class OracleLikePasswordHasher {
    public static void main(String[] args) {
        System.out.println(new OracleLikePasswordHasher().generateHash("user", "password"));
    }

    public String generateHash(String user, String password) {
        // instructions see http://www.sans.org/reading_room/special/?id=oracle_pass&ref=911
        // 1. Concatenate the username and the password to produce a plaintext string;
        // 2. Convert the plaintext string to uppercase characters;
        // 3. Convert the plaintext string to multi-byte storage format; ASCII characters have the high byte set to 0x00;

        // can I ignore 3. , cause Java uses Unicode?
        String input = (user + password).toUpperCase();
        for (int i = input.length(); i < 80; i++) {
            input += "0";
        }
        try {
            //4.Encrypt the plaintext string (padded with 0s if necessary to the next even block length)
            //using the DES algorithm in cipher block chaining (CBC) mode with a fixed key value of
            //0x0123456789ABCDEF;

            byte[] keyBytes =  new BigInteger("0123456789ABCDEF", 16).toByteArray();
            Cipher cipherA = Cipher.getInstance("DES/CBC/NoPadding");
            SecretKey key = new SecretKeySpec(keyBytes, "DES");

            IvParameterSpec ips = new IvParameterSpec(new byte[] {0,0,0,0,0,0,0,0});
            cipherA.init(Cipher.ENCRYPT_MODE, key, ips);        
            byte[] encryptedBytes = cipherA.doFinal(input.getBytes());    

            //5.Encrypt the plaintext string again with DES-CBC, but using the last block of the output
            //of the previous step (ignoring parity bits) as the encryption key.         
            encryptedBytes = addParity(encryptedBytes);
            Key encryptedKey = new SecretKeySpec(encryptedBytes, "DES");
            Cipher cipherB = Cipher.getInstance("DES/CBC/NoPadding");
            cipherB.init(Cipher.ENCRYPT_MODE, encryptedKey, ips);
            byte[] encryptedPw = cipherB.doFinal(input.getBytes());

            //The last block of the output is converted into a printable string to produce the password hash value.
            return new sun.misc.BASE64Encoder().encode(encryptedPw);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }    

    // copied from http://www.exampledepot.com/egs/javax.crypto/MakeDes.html
    // Takes a 7-byte quantity and returns a valid 8-byte DES key.
    // The input and output bytes are big-endian, where the most significant
    // byte is in element 0.
    private byte[] addParity(byte[] in) {
        byte[] result = new byte[8];

        // Keeps track of the bit position in the result
        int resultIx = 1;

        // Used to keep track of the number of 1 bits in each 7-bit chunk
        int bitCount = 0;

        // Process each of the 56 bits
        for (int i=0; i<56; i++) {
            // Get the bit at bit position i
            boolean bit = (in[6-i/8]&(1<<(i%8))) > 0;

            // If set, set the corresponding bit in the result
            if (bit) {
                result[7-resultIx/8] |= (1<<(resultIx%8))&0xFF;
                bitCount++;
            }

            // Set the parity bit after every 7 bits
            if ((i+1) % 7 == 0) {
                if (bitCount % 2 == 0) {
                    // Set low-order bit (parity bit) if bit count is even
                    result[7-resultIx/8] |= 1;
                }
                resultIx++;
                bitCount = 0;
            }
            resultIx++;
        }
        return result;
    }
} 

Comments

843811
It is important to read the specification properly -
import java.util.Arrays;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class OracleLikePasswordHasher
{
    private static final byte[] keyBytes =
    {
        (byte) 0x01, (byte) 0x23, (byte) 0x45, (byte) 0x67, (byte) 0x89, (byte) 0xab, (byte) 0xcd, (byte) 0xef
    };
    private static final IvParameterSpec ips = new IvParameterSpec(new byte[8]);

    public static void main(String[] args) throws Exception
    {
        System.out.println(OracleLikePasswordHasher.generateHash("user", "password"));
    }

    public static String generateHash(String user, String password) throws Exception
    {
        // instructions see http://www.sans.org/reading_room/special/?id=oracle_pass&ref=911
        // 1. Concatenate the username and the password to produce a plaintext string;
        // 2. Convert the plaintext string to uppercase characters;
        // 3. Convert the plaintext string to multi-byte storage format; ASCII characters have the high byte set to 0x00;
        byte[] input = (user + password).toUpperCase().getBytes("utf-16be");

        //4.Encrypt the plaintext string (padded with 0s if necessary to the next even block length)
        //using the DES algorithm in cipher block chaining (CBC) mode with a fixed key value of
        //0x0123456789ABCDEF;
        input = Arrays.copyOf(input, ((input.length + 7) / 8) * 8); // Pad with zeros
     
        Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
        SecretKey key = new SecretKeySpec(keyBytes, "DES");
        cipher.init(Cipher.ENCRYPT_MODE, key, ips);
        byte[] encryptedBytes = cipher.doFinal(input);

        //5.Encrypt the plaintext string again with DES-CBC, but using the last block of the output
        //of the previous step (ignoring parity bits) as the encryption key.
        // Don't need to set parity - done behind the scenes by the JCE
        Key encryptedKey = new SecretKeySpec(encryptedBytes, encryptedBytes.length - 8, 8, "DES");
        cipher.init(Cipher.ENCRYPT_MODE, encryptedKey, ips);
        byte[] encryptedPw = cipher.doFinal(input);

        //The last block of the output is converted into a printable string to produce the password hash value.
        return bytesToHexString(encryptedPw, encryptedPw.length - 8, 8);
    }

    private static String bytesToHexString(byte[] bytes, int offset, int length)
    {
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++)
        {
            sb.append(String.format("%02X", bytes[offset++]));
        }
        return sb.toString();
    }
}
Edited by: sabre150 on Nov 17, 2009 4:42 PM

Reduced the amount of code.
843811
Wow! Your code really puts out the right hash - thanks a lot!
Robby
1 - 2
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Dec 16 2009
Added on Nov 17 2009
2 comments
443 views