smartcardpbkdf2

Problem using PBKDF2 algorithm on javacard to convert mnemonic phrase to seed


I am trying to Convert cryptocurrency mnemonic phrase to seed using a smart card. I have seen this question and the perfect answer. I am trying to use the same code for my conversion with a little modification I have made. This is the applet code:

    import javacard.framework.*;
    
    /**
     *
     * @author Matej Evin
     * 6th March 2018
     */
     
    public class PBKDF2Applet extends javacard.framework.Applet
    {
        final static byte CLA_SIMPLEAPPLET              = (byte) 0xB0;
        final static byte INS_MESSAGE_DIGEST            = (byte) 0x61;
        final static short ARRAY_LENGTH                 = (short) 0xff;
        final static short SW_OBJECT_NOT_AVAILABLE      = (short) 0x6711;
    
        private   PBKDF2  m_pbkdf2 = null;
        private byte m_ramArray1[] = null;
        private byte m_ramArray2[] = null;
        private byte m_ramArray3[] = null;
        private byte password[] = null;
        private final byte salt[] = {(byte)'m',(byte)'n',(byte)'e',(byte)'m',(byte)'o',(byte)'n',(byte)'i',(byte)'c'};
        private byte m_dataArray[] = null;
    
        protected PBKDF2Applet(byte[] buffer, short offset, byte length)
        {
            short dataOffset = offset;
            boolean isOP2 = false;
    
            if(length > 9) {
                
                dataOffset += (short)( 1 + buffer[offset]);
                dataOffset += (short)( 1 + buffer[dataOffset]);
                dataOffset++;
    
                m_dataArray = new byte[ARRAY_LENGTH];
                Util.arrayFillNonAtomic(m_dataArray, (short) 0, ARRAY_LENGTH, (byte) 0);
                
                m_ramArray1 = JCSystem.makeTransientByteArray((short) 0xff, JCSystem.CLEAR_ON_DESELECT);
                m_ramArray2 = JCSystem.makeTransientByteArray((short) 0xff, JCSystem.CLEAR_ON_DESELECT);
                m_ramArray3 = JCSystem.makeTransientByteArray((short) 0xff, JCSystem.CLEAR_ON_DESELECT);
                password    = JCSystem.makeTransientByteArray((short) 0xff, JCSystem.CLEAR_ON_DESELECT);
                
                m_pbkdf2 = PBKDF2.getInstance(PBKDF2.ALG_SHA_512);
    
                isOP2 = true;
    
            } else {}
                register();
        }
        
        public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException
        {
            new PBKDF2Applet(bArray, bOffset, bLength);
        }
    
        public boolean select()
        {
            return true;
        }
    
        public void deselect()
        {
            return;
        }
    
        public void process(APDU apdu) throws ISOException
        {
            byte[] apduBuffer = apdu.getBuffer();
            
            if (selectingApplet())
                return;
    
            if (apduBuffer[ISO7816.OFFSET_CLA] == CLA_SIMPLEAPPLET) {
                switch ( apduBuffer[ISO7816.OFFSET_INS] )
                {               
                    case INS_MESSAGE_DIGEST:
                        Pbkdf2(apdu);
                    break;
                    
                    default :
                        ISOException.throwIt( ISO7816.SW_INS_NOT_SUPPORTED ) ;
                    break ;
    
                }
            }
            else ISOException.throwIt( ISO7816.SW_CLA_NOT_SUPPORTED);
        }
        
        
        public void Pbkdf2(APDU apdu){
            byte[]    buffer = apdu.getBuffer();
            short     passLen = apdu.setIncomingAndReceive();
            Util.arrayCopyNonAtomic(buffer, (short)ISO7816.OFFSET_CDATA , password, (short)0, passLen);
                                         
            short retLen = m_pbkdf2.doFinal(password, (short) 0, passLen,
                                         salt, (short) 0, (short) 8,
                                         (short) 2048,
                                         m_ramArray3, (short) 0);
            
            Util.arrayCopyNonAtomic(m_ramArray3, (short) 0, buffer, (short)0, retLen);
            apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, retLen);
        }
    }

and this is the pbkdf2 class where I have added sha512 supported in Javacard 3.0.5 and above:

    import javacard.framework.JCSystem;
    import javacard.framework.Util;
    import javacard.security.MessageDigest;
    import javacard.security.CryptoException;
    import javacard.security.HMACKey;
    import javacard.security.KeyBuilder;
    import javacard.security.Signature;
    
    
    /**
     *
     * @author Matej Evin
     * 03 August 2018
     */
    
    public class PBKDF2 {
        
        public static final byte  ALG_SHA            = 1;
        public static final byte  ALG_SHA_256        = 4;
        public static final byte  ALG_SHA_512        = 6;
        public static final byte  LENGTH_SHA         = 20;
        public static final byte  LENGTH_SHA_256     = 32;
        public static final byte  LENGTH_SHA_512     = 64;
        public static final short BLOCKSIZE_SHA      = 128;
        private final static short SALT_LEN          = 16;
        private final static short  SZERO           = (short) 0x0;
        private final static byte   BZERO           = (byte)  0x0;
        private final static byte   BONE            = (byte)  0x01;
        private static short mdlen;
        
        private byte[] U_i                          = null;
        private byte[] keyprime                     = null;
        private byte[] innerblock                   = null;
        private byte[] outerblock                   = null;
        
        private         MessageDigest   m_hash      = null;
        private static  PBKDF2          m_instance  = null;
        
        protected PBKDF2() {}
        
        public static PBKDF2 getInstance(byte algorithm) throws CryptoException {
            if (m_instance == null)
            {
                m_instance = new PBKDF2();
                
                switch(algorithm) {
                    case ALG_SHA:
                        m_instance.m_hash = MessageDigest.getInstance(ALG_SHA, false);
                        PBKDF2.mdlen  = LENGTH_SHA;
                        break;
                    case ALG_SHA_256:
                        m_instance.m_hash = MessageDigest.getInstance(ALG_SHA_256, false);
                        PBKDF2.mdlen  = LENGTH_SHA_256;
                        break;
                    case ALG_SHA_512:
                        m_instance.m_hash = MessageDigest.getInstance(ALG_SHA_512, false);
                        PBKDF2.mdlen  = LENGTH_SHA_512;
                        break;
                    default:
                        throw new CryptoException(CryptoException.NO_SUCH_ALGORITHM);
                }
                
                m_instance.U_i        = JCSystem.makeTransientByteArray(mdlen, JCSystem.CLEAR_ON_DESELECT);                         
                m_instance.keyprime   = JCSystem.makeTransientByteArray(BLOCKSIZE_SHA, JCSystem.CLEAR_ON_DESELECT);                 
                m_instance.innerblock = JCSystem.makeTransientByteArray((short)(BLOCKSIZE_SHA + mdlen), JCSystem.CLEAR_ON_DESELECT);
                m_instance.outerblock = JCSystem.makeTransientByteArray((short)(BLOCKSIZE_SHA + mdlen), JCSystem.CLEAR_ON_DESELECT);
            }
            return m_instance;
        }
        
        private short hmac(byte[] K, short KOffset, short KLength, short mLength) {
            
            if (KLength < BLOCKSIZE_SHA) {
                Util.arrayFillNonAtomic(keyprime, KLength, (short)(BLOCKSIZE_SHA-KLength), BZERO);
                Util.arrayCopyNonAtomic(K, KOffset, keyprime, SZERO, KLength);
            } else
            if (KLength > BLOCKSIZE_SHA)
            {
                m_hash.doFinal(K, KOffset, KLength, keyprime, SZERO);
            } else {
                Util.arrayCopyNonAtomic(K, KOffset, keyprime, SZERO, KLength);
            }
            
            for (short i = 0; i < BLOCKSIZE_SHA; i++) {
                innerblock[i] = (byte) (keyprime[i] ^ ((byte) 0x36));
                outerblock[i] = (byte) (keyprime[i] ^ ((byte) 0x5c));
            }
            
            Util.arrayCopyNonAtomic(U_i, SZERO, innerblock, BLOCKSIZE_SHA, mLength);
            m_hash.doFinal(innerblock, SZERO, (short) (BLOCKSIZE_SHA+mLength), innerblock, SZERO);
            
            Util.arrayCopyNonAtomic(innerblock, SZERO, outerblock, BLOCKSIZE_SHA, mdlen);
            return m_hash.doFinal(outerblock, SZERO, (short) (BLOCKSIZE_SHA+mdlen), U_i, SZERO);
        }
        
        public short doFinal(byte[] password, short passwordOffset, short passwordLength,
                             byte[] salt, short saltOffset, short saltLength,
                             short iterations,
                             byte[] out, short outOffset) throws CryptoException {
            
            if (SALT_LEN < saltLength)
                throw new CryptoException(CryptoException.ILLEGAL_USE);
            
            Util.arrayCopyNonAtomic(salt, saltOffset, U_i, SZERO, saltLength);
            U_i[         saltLength     ] = BZERO;
            U_i[(short) (saltLength + 1)] = BZERO;
            U_i[(short) (saltLength + 2)] = BZERO;
            U_i[(short) (saltLength + 3)] = BONE;
            
            hmac(password, passwordOffset, passwordLength, (short) (saltLength + 4));
            Util.arrayCopyNonAtomic(U_i, SZERO, out, outOffset, mdlen);
            for (short k = 1; k < iterations; k++) {
                hmac(password, passwordOffset, passwordLength, mdlen);
                for (short j = 0; j < mdlen; j++) {
                    out[(short) (outOffset + j)] ^= U_i[j];
                }
            }
            
            return mdlen;
        }
    }

As I wanted to implement mnemonic to seed algorithm as described in this link, I have used a mnemonic phrase randomly generated here as:

broken crazy ankle modify false review ship sell beyond suspect orchard acoustic plate client design soup olympic curve treat retreat crunch funny next mammal

which should result in the 64-byte seed below calculated by the same site:

392b80adbd0ebb1d629410a187cdf74ae8ee18a70d11171e553adf5fd876b4173cd618d81f442120eefdf0cd5a92cdb0a6f79b6d404e326f68ab5e99b8a38d5e

but, unfortunately, my code above results in this seed:

0EBB1D629410A187CDF74AE8EE18A70D11171E553ADF5FD876B4173CD618D81F442120EEFDF0CD5A92CDB0A6F79B6D404E326F68AB5E99B8A38D5E7065637420

I don't know where did I make a mistake modifying the code!

Thanks in advance,


Solution

  • Your code ends with apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, retLen); but the line before you do Util.arrayCopyNonAtomic(m_ramArray3, (short) 0, buffer, (short)0, retLen); where you copy to offset 0 in your buffer. As a result you are sending from buffer at an offset of 5 but the response you want starts at 0. Changing the code to apdu.setOutgoingAndSend((short) 0, retLen); would solve the problem.