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,
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.