SCP11Lib consists of multiple functions that are used when the secure channel is opened.
This function is core. Here, we use the KDF object of KDF2BytesGenerator of the algorithm library, directly initialize the corresponding secret and SharedInfo information, and then call the generateBytes() interface to generate the session key. Of course, the most important parameter is the length of the key to be generated.
to generate the MAC of session, also the receipt. it's is the same with SCP03.
Code: Select all
/**
* @file SCP11.java
* @brief The implementation of SCP11.
* @author SCPlatform@outlook.com
* @version 1.0
* @date 2018-06-25
*
* @copyright SCPlatform@outlook.com All rights reserved.
*/
package com.scplatform.scp11;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.KeyAgreement;
import javax.smartcardio.CommandAPDU;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
import org.bouncycastle.crypto.macs.CMac;
import org.bouncycastle.crypto.params.KDFParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
public class SCP11Lib {
public static final short SEC_TRUE = 0x5A5A;
public static final short SEC_FALSE = (short) 0xA5A5;
public static byte[] pad80(byte[] text, int offset, int length, int blocksize) {
if (length == -1) {
length = text.length - offset;
}
int totalLength = length;
for (totalLength++; (totalLength % blocksize) != 0; totalLength++) {
;
}
int padlength = totalLength - length;
byte[] result = new byte[totalLength];
System.arraycopy(text, offset, result, 0, length);
result[length] = (byte) 0x80;
for (int i = 1; i < padlength; i++) {
result[length + i] = (byte) 0x00;
}
return result;
}
public static byte[] pad80(byte[] text, int blocksize) {
return pad80(text, 0, text.length, blocksize);
}
public static byte[] unpad80(byte[] text) throws Exception {
if (text.length < 1)
throw new Exception("Invalid ISO 7816-4 padding");
int offset = text.length - 1;
while (offset > 0 && text[offset] == 0) {
offset--;
}
if (text[offset] != (byte) 0x80) {
throw new Exception("Invalid ISO 7816-4 padding");
}
return Arrays.copyOf(text, offset);
}
private static void buffer_increment(byte[] buffer, int offset, int len) {
if (len < 1)
return;
for (int i = offset + len - 1; i >= offset; i--) {
if (buffer[i] != (byte) 0xFF) {
buffer[i]++;
break;
} else
buffer[i] = (byte) 0x00;
}
}
public static void buffer_increment(byte[] buffer) {
buffer_increment(buffer, 0, buffer.length);
}
/**
* Verify certificate by parent public key.
*
* @param cert
* Certificate which to be verified, the element of 5F73 mast be
* present.
* @param parentPubKey
* public key object used to verify the cert.
* @param hash
* Message digest object
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
* @return 0x5A5A:success, 0xA5A5:false
*/
public static short verifyCert(CertificateSCP11 cert, PublicKey parentPubKey)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
byte[] message = cert.getSignatureMessage();
byte[] sigBytes = cert.getSignatureBytes();
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
// verify a signature
signature.initVerify(parentPubKey);
signature.update(message);
if (signature.verify(sigBytes)) {
return SEC_TRUE;
} else {
return SEC_FALSE;
}
}
/**
* Signature the certificate by parent_privatekey
*
* @param cert
* Certificate which content the data to be sign.
* @param parentPrivKey
* parent private key.
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
*/
public static byte[] signCertificate(CertificateSCP11 cert, PrivateKey parentPrivKey)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException {
byte[] message = cert.getSignatureMessage();
Signature signature = Signature.getInstance("ECDSA", "BC");
// generate a signature
signature.initSign(parentPrivKey);
signature.update(message);
byte[] sigBytes = signature.sign();
return sigBytes;
}
/**
* X9.63 key derivation function, Derive AES Session key from secret and
* sharedInfo.
*
* @param zab
* secret
* @param sharedInfo
* sharedInfo
* @param keydatalen
* the byte length of the keying data to generate.e.g. 16*5
* @return 5 keys
*/
public static byte[] deriveFunction(byte[] za, byte[] zb, byte[] sharedInfo, int keyDataLen) {
int len = za.length + zb.length;
byte[] secret = new byte[len];
byte[] keys = new byte[keyDataLen];
short offset = 0;
System.arraycopy(za, 0, secret, offset, za.length);
offset += za.length;
System.arraycopy(zb, 0, secret, offset, zb.length);
offset += zb.length;
SHA256Digest hash = new SHA256Digest();
KDF2BytesGenerator kdf = new KDF2BytesGenerator(hash);
kdf.init(new KDFParameters(secret, sharedInfo));
kdf.generateBytes(keys, (short) 0, keyDataLen);
return keys;
}
/**
* genCMAC
*
* @param key
* @param msg
* @return
*/
public static byte[] genCMAC(byte[] key, byte[] msg) {
BlockCipher cipher = new AESEngine();
CMac cmac = new CMac(cipher);
cmac.init(new KeyParameter(key));
cmac.update(msg, 0, msg.length);
byte[] out = new byte[cmac.getMacSize()];
cmac.doFinal(out, 0);
return out;
}
/**
* verifyReceipt in order to verify the device.
* @param str_pk_sd_ecka the public key of device.
* @param str_sk_oce_ecka the private key of server.
* @param str_epk_oce_ecka the ephemeral public key of server.
* @param str_esk_oce_ecka the ephemeral private key of server
* @param card_group_id the value of tag 5F20 in certificate.
* @param crtA6 an object of CERTA6.
* @param keyLength the session key length.
* @param curve value of EC_Curve.
* @param comparedReceipt the receipt which to be compared.
* @return
*/
public static boolean verifyReceipt(String str_pk_sd_ecka, String str_sk_oce_ecka, String str_epk_oce_ecka,
String str_esk_oce_ecka, String card_group_id, CRTA6 crtA6, short keyLength, EC_Curve curve,
String comparedReceipt) {
byte[] mac = null;
byte[] comparedMAC = Hex.decode(comparedReceipt);
try {
byte[] sharedInfoFromA6 = crtA6.getSharedInfo();
byte[] shared_info = concateSharedInfo(sharedInfoFromA6, null, null, Hex.decode(card_group_id));
ECPublicKey pk_sd_ecka = curve.buildPubKeyFromValue(Hex.decode(str_pk_sd_ecka));
ECPrivateKey sk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_sk_oce_ecka));
ECPrivateKey esk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_esk_oce_ecka));
byte[] sessionKey = deriveSessionKey(pk_sd_ecka, sk_oce_ecka, esk_oce_ecka, shared_info, (short) (keyLength*5));
byte[] receiptKey = Arrays.copyOfRange(sessionKey, 0, keyLength);
mac = generateReceipt(receiptKey, crtA6, str_epk_oce_ecka, str_pk_sd_ecka);
mac = Arrays.copyOfRange(mac, 0, comparedMAC.length);
} catch (Exception ex) {
ex.printStackTrace();
}
return Arrays.areEqual(mac, comparedMAC);
}
/**
* deriveSessionKey
*
* @param pk_sd_ecka
* @param sk_oce_ecka
* @param esk_oce_ecka
* @param shared_info
* @param keyLength
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeyException
*/
public static byte[] deriveSessionKey(ECPublicKey pk_sd_ecka, ECPrivateKey sk_oce_ecka, ECPrivateKey esk_oce_ecka,
byte[] shared_info, int keyLength)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {
KeyAgreement aKeyAgree = KeyAgreement.getInstance("ECDH", "BC");
MessageDigest hash = MessageDigest.getInstance("SHA1", "BC");
aKeyAgree.init(esk_oce_ecka);
aKeyAgree.doPhase(pk_sd_ecka, true);
// generate the key bytes
byte[] retx = aKeyAgree.generateSecret();
byte[] shses = hash.digest(retx);
aKeyAgree = KeyAgreement.getInstance("ECDH", "BC");
hash = MessageDigest.getInstance("SHA1", "BC");
aKeyAgree.init(sk_oce_ecka);
aKeyAgree.doPhase(pk_sd_ecka, true);
hash.reset();
byte[] ret = aKeyAgree.generateSecret();
byte[] shsss = hash.digest(ret);
return deriveFunction(shses, shsss, shared_info, keyLength);
}
/**
* concate the SharedInfo for Key Derivation
*
* @return
*/
public static byte[] concateSharedInfo(byte[] sharedInfo, byte[] sin, byte[] sdin, byte[] card_group_id) {
// byte[] sharedInfo = crtA6.getSharedInfo;
byte[] sharedInfox = null;
short len = (short) sharedInfo.length;
short offset = 0;
if (card_group_id != null) {
len += card_group_id.length;
sharedInfox = new byte[len];
System.arraycopy(sharedInfo, 0, sharedInfox, offset, sharedInfo.length);
offset += sharedInfo.length;
System.arraycopy(card_group_id, 0, sharedInfox, offset, card_group_id.length);
} else {
len += sin.length;
len += sdin.length;
sharedInfox = new byte[len];
System.arraycopy(sharedInfo, 0, sharedInfox, offset, sharedInfo.length);
offset += sharedInfo.length;
System.arraycopy(sin, 0, sharedInfox, offset, sin.length);
offset += sin.length;
System.arraycopy(sdin, 0, sharedInfox, offset, sdin.length);
}
return sharedInfox;
}
/**
* generateReceipt
*
* @param receiptKey
* @param crtA6
* @param epk_oce_ecka
* @param pk_sd_ecka
* @return
*/
public static byte[] generateReceipt(byte[] receiptKey, CRTA6 crtA6, String epk_oce_ecka, String pk_sd_ecka) {
byte[] crtA6Data = crtA6.toBytes();
TLVUtil _5F49epk_oce = new TLVUtil(new byte[]{(byte) 0x5F, 0x49}, Hex.decode(epk_oce_ecka));
TLVUtil _5F49pk_sd = new TLVUtil(new byte[]{(byte) 0x5F, 0x49}, Hex.decode(pk_sd_ecka));
byte[] receiptInput = new byte[crtA6Data.length + _5F49epk_oce.size() + _5F49pk_sd.size()];
short offset = 0;
System.arraycopy(crtA6Data, 0, receiptInput, offset, crtA6Data.length);
offset += crtA6Data.length;
offset = _5F49epk_oce.toBytes(receiptInput, offset);
offset = _5F49pk_sd.toBytes(receiptInput, offset);
return genCMAC(receiptKey, receiptInput);
}
/**
* Convert CAP files to download scripts.
* @param scriptCmd apdu command list.
* @param str_pk_sd_ecka the public key of device.
* @param str_sk_oce_ecka the private key of server.
* @param str_epk_oce_ecka the ephemeral public key of server.
* @param str_esk_oce_ecka the ephemeral private key of server.
* @param card_group_id the value of tag 5F20 in certificate.
* @param crtA6 an object of CRTA6.
* @param keyLength the length of session key length, default is 16 bytes.
* @param curve value of EC_Curve, default value is EC_Curve.SECP_256k1
* @return
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeySpecException
*/
public static List<String> generateScript(List<CommandAPDU> scriptCmd, String str_pk_sd_ecka, String str_sk_oce_ecka,
String str_epk_oce_ecka, String str_esk_oce_ecka, String card_group_id, CRTA6 crtA6, short keyLength, EC_Curve curve)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
byte[] sharedInfoFromA6 = crtA6.getSharedInfo();
byte[] shared_info = concateSharedInfo(sharedInfoFromA6, null, null, Hex.decode(card_group_id));
short kLen = keyLength;
ECPublicKey pk_sd_ecka = curve.buildPubKeyFromValue(Hex.decode(str_pk_sd_ecka));
ECPrivateKey sk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_sk_oce_ecka));
ECPrivateKey esk_oce_ecka = curve.buildPrivKeyFromValue(Hex.decode(str_esk_oce_ecka));
byte[] sessionKey = deriveSessionKey(pk_sd_ecka, sk_oce_ecka, esk_oce_ecka, shared_info, (short) (keyLength*5));
byte[] receiptKey = Arrays.copyOfRange(sessionKey, 0, kLen);
byte[] mac_chain = generateReceipt(receiptKey, crtA6, str_epk_oce_ecka, str_pk_sd_ecka);
SCP11StaticInfo staticInfo = new SCP11StaticInfo();
// staticInfo
SCP11 scp11 = new SCP11(staticInfo, null);
scp11.setSkey_receipt(receiptKey);
scp11.setSkey_enc(Arrays.copyOfRange(sessionKey, 1 * keyLength, 2*keyLength));
scp11.setSkey_mac(Arrays.copyOfRange(sessionKey, 2 * keyLength, 3*keyLength));
scp11.setSkey_rmac(Arrays.copyOfRange(sessionKey, 3 * keyLength, 4*keyLength));
scp11.setSkey_dek(Arrays.copyOfRange(sessionKey, 4 * keyLength, 5*keyLength));
scp11.setMac_chaining(mac_chain);
byte[] kUsage = crtA6.get_keyUsage();
short keyUsage = kUsage.length == 1?kUsage[0]:TLVUtil.getShort(kUsage, (short) 0);
byte sLevel = SecureChannel.getSecureLevelFromKeyUsage(keyUsage);
scp11.setsLevel(sLevel);
scp11.setHasAuthed(true);
List<String> wrapCmdLst = new ArrayList<String>();
for (CommandAPDU cmd : scriptCmd) {
String apdu = Hex.toHexString(scp11.wrap(cmd).getBytes()).toUpperCase();
wrapCmdLst.add(apdu);
}
return wrapCmdLst;
}
/**
* publicKey2String
* @param pubKey
* @return
*/
public static String publicKey2String(ECPublicKey pubKey)
{
byte[] pubx = pubKey.getW().getAffineX().toByteArray();
byte[] puby = pubKey.getW().getAffineY().toByteArray();
if(pubx[0] == 0)
{
pubx = Arrays.copyOfRange(pubx, 1, pubx.length);
}
if(puby[0] == 0)
{
puby = Arrays.copyOfRange(puby, 1, puby.length);
}
String wx = Hex.toHexString(pubx);//maybe leading with 0x00
String wy = Hex.toHexString(puby);//maybe leading with 0x00
String epk_oce_ecka_value = "04" + wx + wy;
return epk_oce_ecka_value;
}
/**
* privateKey2String
* @param privKey
* @return
*/
public static String privateKey2String(ECPrivateKey privKey)
{
byte[] priv = privKey.getS().toByteArray();
if(priv[0] == 0)
{
priv = Arrays.copyOfRange(priv, 1, priv.length);
}
return Hex.toHexString(priv);
}
}