一、用处
- SPAKE2+: an ECC-based pairing algorithm protocol, mutually authenticate two entities.
- The NIST P-256 curve shall be used.
二、过程
1、Vehicle OEM Server:generate password, w0, L
Vehicle OEM Server 应该在配对前生成w0,L并存入车辆,以便在车辆断网时配对也可以继续。
(1)Scrypt = f(password, salt, cost, r, p, dkLen)
password: 随机数(可选:8个十进制数字组成的字符串)(The password is UTF-8 encoded).
salt: 随机生成的字母字符串。(16个随机字母组成的字符串)
cost:cost parameter Nscrypt: 4096 or higher(可选:32768)
r: block size,8
p: parallelization parameter, 1
dkLen: Output length, 80
Scrypt代码见下方
(2)z0: scrypt的左40字节;z1:scrypt的右40字节。
(3)w0=(z0 mod (n-1)) + 1; w1=(z1 mod (n-1))+1
n:the order of base point G as defined for NIST P-256
(4) L=w1 × G
2、Vehicle
The Vehicle OEM Server provides salt, L, w0 to the vehicle.(数据传输应保持安全和完整)
Y = y × G + w0 × N
Z = y × (X - w0 × M)
V = y × L
(x,y均为随机数,并且无法从X,Y获取x,y)
(X, Y shall not be the point at infinity and shall be on the chosen curve. A new y shall be generated, and a new Y shall be calculated until the requirements for Y are fulfilled before continuing the protocol.)
(M,N is a point on the used elliptic curve,算法见下方)
3、Device/owner
The password should be provided to the owner through the Vehicle OEM account,protected by the login credentials known only by the owner.
X = x × G + w0 × M
Z = x × (Y - w0 × N)
V = w1 × (Y - w0 × N)
4、Shared key
Vehicle 和 device交换X、Y后经计算得:
Z = xyG
V = xyw1
Both vehicle and device then shall calculate the shared secret K as follows:
K = SHA-256(len(X) || X || len(Y) || Y || len(Z) || Z || len(V) || V || len(w0) || w0)
where len(str) denotes the length of a string in bytes, represented as an 8-byte little-endian number.
The shared secrets CK and SK are derived from K:
CK = K [0:128]
SK = K [128:128] 3
The LONG_TERM_SHARED_SECRET is derived based on Listing 18-9.
5、认证
The verification of the derived keys shall be done by calculating a check value on either side which are mutually exchanged and verified.
The vehicle shall calculate the verification value M[1] as
K1=HKDF(CK, “ConfirmationKeys” || TLV 5Bh || TLV 5Ch, [0:128]), where ConfirmationKeys is a defined static string (see Listing 18-6).
M[1]=C-MAC(K1, X) using K1 as secret key, using CMAC-AES-128 as defined in [RFC4493] and shall send it over to the device.
The device shall verify M[1] using the received M[1] from the vehicle and, if successful, shall
calculate the verification value M[2] as
K2=HKDF(CK, “ConfirmationKeys” || TLV 5Bh || TLV 5Ch, [128:128]) (see Listing 18-6)
M[2]=C-MAC(K2, Y) using K2 as secret key, using CMAC-AES-128 as defined in [RFC4493] and shall send it over to the vehicle. 31
The protocol succeeds when the vehicle successfully validates M[2].
6、相关参数代码
计算Scrypt
private static byte[] scrypt(byte[] password, byte[] salt, int cost,
int blocksize, int parallel, int length)
throws GeneralSecurityException {
if (cost < 2 || (cost & (cost - 1)) != 0) {
throw new IllegalArgumentException(
"Cost must be a power of 2 greater than 1");
}
if (cost > Integer.MAX_VALUE / 128 / blocksize) {
throw new IllegalArgumentException("Parameter cost is too large");
}
if (blocksize > Integer.MAX_VALUE / 128 / parallel) {
throw new IllegalArgumentException(
"Parameter blocksize is too large");
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(password, "HmacSHA256"));
byte[] key = new byte[length];
byte[] b1 = new byte[128 * blocksize * parallel];
byte[] xy = new byte[256 * blocksize];
byte[] v1 = new byte[128 * blocksize * cost];
pbkdf2(mac, salt, 1, b1, parallel * 128 * blocksize);
for (int i = 0; i < parallel; i++) {
smix(b1, i * 128 * blocksize, blocksize, cost, v1, xy);
}
pbkdf2(mac, b1, 1, key, length);
return key;
}
protected static void pbkdf2(Mac mac, byte[] salt, int iterations,
byte[] key, int length) throws GeneralSecurityException {
int len = mac.getMacLength();
byte[] u1 = new byte[len];
byte[] t1 = new byte[len];
byte[] block = new byte[salt.length + 4];
int limit = (int) Math.ceil((double) length / len);
int r = length - (limit - 1) * len;
System.arraycopy(salt, 0, block, 0, salt.length);
for (int i = 1; i <= limit; i++) {
block[salt.length + 0] = (byte) (i >> 24 & 0xff);
block[salt.length + 1] = (byte) (i >> 16 & 0xff);
block[salt.length + 2] = (byte) (i >> 8 & 0xff);
block[salt.length + 3] = (byte) (i >> 0 & 0xff);
mac.update(block);
mac.doFinal(u1, 0);
System.arraycopy(u1, 0, t1, 0, len);
for (int j = 1; j < iterations; j++) {
mac.update(u1);
mac.doFinal(u1, 0);
for (int k = 0; k < len; k++) {
t1[k] ^= u1[k];
}
}
System.arraycopy(t1, 0, key, (i - 1) * len, (i == limit ? r : len));
}
}
w0 = Computation_w(scrypt, 0);
w1 = Computation_w(scrypt, 40);
protected BigInteger Computation_w(byte[] data, int offset) {
BigInteger zx;
if (data[offset] < 0) {
byte[] byte41 = new byte[41];
byte41[0] = 0;
System.arraycopy(data, offset, byte41, 1, 40);
zx = new BigInteger(byte41);
} else {
byte[] byte40 = new byte[40];
System.arraycopy(data, offset, byte40, 0, 40);
zx = new BigInteger(byte40);
}
BigInteger wx = zx.mod(n);
return wx.add(new BigInteger("1"));
}
protected ECPoint Computation_L() {
L = G.multiply(w1).normalize();
log.info("L: " + L);
return L;
}
private static void smix(byte[] b1, int bi, int round, int cpu, byte[] v1, byte[] xy) {
int xi = 0;
int yi = 128 * round;
System.arraycopy(b1, bi, xy, xi, 128 * round);
for (int i = 0; i < cpu; i++) {
System.arraycopy(xy, xi, v1, i * (128 * round), 128 * round);
blockMixSalsa8(xy, xi, yi, round);
}
for (int i = 0; i < cpu; i++) {
int j = integerify(xy, xi, round) & (cpu - 1);
blockxor(v1, j * (128 * round), xy, xi, 128 * round);
blockMixSalsa8(xy, xi, yi, round);
}
System.arraycopy(xy, xi, b1, bi, 128 * round);
}
private static void blockMixSalsa8(byte[] by, int bi, int yi, int round) {
byte[] x1 = new byte[64];
System.arraycopy(by, bi + (2 * round - 1) * 64, x1, 0, 64);
for (int i = 0; i < 2 * round; i++) {
blockxor(by, i * 64, x1, 0, 64);
salsa(x1);
System.arraycopy(x1, 0, by, yi + (i * 64), 64);
}
for (int i = 0; i < round; i++) {
System.arraycopy(by, yi + (i * 2) * 64, by, bi + (i * 64), 64);
}
for (int i = 0; i < round; i++) {
System.arraycopy(by, yi + (i * 2 + 1) * 64, by, bi + (i + round) * 64, 64);
}
}
private static void salsa(byte[] b1) {
int[] base32 = new int[16];
for (int i = 0; i < 16; i++) {
base32[i] = (b1[i * 4 + 0] & 0xff) << 0;
base32[i] |= (b1[i * 4 + 1] & 0xff) << 8;
base32[i] |= (b1[i * 4 + 2] & 0xff) << 16;
base32[i] |= (b1[i * 4 + 3] & 0xff) << 24;
}
int[] x1 = new int[16];
System.arraycopy(base32, 0, x1, 0, 16);
for (int i = 8; i > 0; i -= 2) {
x1[4] ^= r1(x1[0] + x1[12], 7);
x1[8] ^= r1(x1[4] + x1[0], 9);
x1[12] ^= r1(x1[8] + x1[4], 13);
x1[0] ^= r1(x1[12] + x1[8], 18);
x1[9] ^= r1(x1[5] + x1[1], 7);
x1[13] ^= r1(x1[9] + x1[5], 9);
x1[1] ^= r1(x1[13] + x1[9], 13);
x1[5] ^= r1(x1[1] + x1[13], 18);
x1[14] ^= r1(x1[10] + x1[6], 7);
x1[2] ^= r1(x1[14] + x1[10], 9);
x1[6] ^= r1(x1[2] + x1[14], 13);
x1[10] ^= r1(x1[6] + x1[2], 18);
x1[3] ^= r1(x1[15] + x1[11], 7);
x1[7] ^= r1(x1[3] + x1[15], 9);
x1[11] ^= r1(x1[7] + x1[3], 13);
x1[15] ^= r1(x1[11] + x1[7], 18);
x1[1] ^= r1(x1[0] + x1[3], 7);
x1[2] ^= r1(x1[1] + x1[0], 9);
x1[3] ^= r1(x1[2] + x1[1], 13);
x1[0] ^= r1(x1[3] + x1[2], 18);
x1[6] ^= r1(x1[5] + x1[4], 7);
x1[7] ^= r1(x1[6] + x1[5], 9);
x1[4] ^= r1(x1[7] + x1[6], 13);
x1[5] ^= r1(x1[4] + x1[7], 18);
x1[11] ^= r1(x1[10] + x1[9], 7);
x1[8] ^= r1(x1[11] + x1[10], 9);
x1[9] ^= r1(x1[8] + x1[11], 13);
x1[10] ^= r1(x1[9] + x1[8], 18);
x1[12] ^= r1(x1[15] + x1[14], 7);
x1[13] ^= r1(x1[12] + x1[15], 9);
x1[14] ^= r1(x1[13] + x1[12], 13);
x1[15] ^= r1(x1[14] + x1[13], 18);
}
for (int i = 0; i < 16; ++i) {
base32[i] = x1[i] + base32[i];
}
for (int i = 0; i < 16; i++) {
b1[i * 4 + 0] = (byte) (base32[i] >> 0 & 0xff);
b1[i * 4 + 1] = (byte) (base32[i] >> 8 & 0xff);
b1[i * 4 + 2] = (byte) (base32[i] >> 16 & 0xff);
b1[i * 4 + 3] = (byte) (base32[i] >> 24 & 0xff);
}
}
private static int integerify(byte[] b1, int bi, int round) {
bi += (2 * round - 1) * 64;
int n = (b1[bi + 0] & 0xff) << 0;
n |= (b1[bi + 1] & 0xff) << 8;
n |= (b1[bi + 2] & 0xff) << 16;
n |= (b1[bi + 3] & 0xff) << 24;
return n;
}
private static void blockxor(byte[] s1, int si, byte[] d1, int di, int length) {
for (int i = 0; i < length; i++) {
d1[di + i] ^= s1[si + i];
}
}
NIST P-256(SECP256r1) curve parameters:
M= 04
88 6e 2f 97 ac e4 6e 55 ba 9d d7 24 25 79 f2 99
3b 64 e1 6e f3 dc ab 95 af d4 97 33 3d 8f a1 2f
5f f3 55 16 3e 43 ce 22 4e 0b 0e 65 ff 02 ac 8e
5c 7b e0 94 19 c7 85 e0 ca 54 7d 55 a1 2e 2d 20
N=
04
d8 bb d6 c6 39 c6 29 37 b0 4d 99 7f 38 c3 77 07
19 c6 29 d7 01 4d 49 a2 4b 4f 98 ba a1 29 2b 49
07 d6 0a a6 bf ad e4 50 08 a6 36 33 7f 51 68 c6
4d 9b d3 60 34 80 8c d5 64 49 0b 1e 65 6e db e7
static final ECPoint G;
static final ECPoint M;
static final ECPoint N;
static final BigInteger n;
static {
SecP256R1Curve Curve = new SecP256R1Curve();
byte[] Gx = {(byte) 0x6b, (byte) 0x17, (byte) 0xd1, (byte) 0xf2, (byte) 0xe1, (byte) 0x2c, (byte) 0x42, (byte) 0x47, (byte) 0xf8, (byte) 0xbc, (byte) 0xe6, (byte) 0xe5, (byte) 0x63, (byte) 0xa4, (byte) 0x40, (byte) 0xf2, (byte) 0x77, (byte) 0x03, (byte) 0x7d, (byte) 0x81, (byte) 0x2d, (byte) 0xeb, (byte) 0x33, (byte) 0xa0, (byte) 0xf4, (byte) 0xa1, (byte) 0x39, (byte) 0x45, (byte) 0xd8, (byte) 0x98, (byte) 0xc2, (byte) 0x96};
byte[] Gy = {(byte) 0x4f, (byte) 0xe3, (byte) 0x42, (byte) 0xe2, (byte) 0xfe, (byte) 0x1a, (byte) 0x7f, (byte) 0x9b, (byte) 0x8e, (byte) 0xe7, (byte) 0xeb, (byte) 0x4a, (byte) 0x7c, (byte) 0x0f, (byte) 0x9e, (byte) 0x16, (byte) 0x2b, (byte) 0xce, (byte) 0x33, (byte) 0x57, (byte) 0x6b, (byte) 0x31, (byte) 0x5e, (byte) 0xce, (byte) 0xcb, (byte) 0xb6, (byte) 0x40, (byte) 0x68, (byte) 0x37, (byte) 0xbf, (byte) 0x51, (byte) 0xf5};
G = Curve.createPoint(new BigInteger(Gx), new BigInteger(Gy));
byte[] Mx = {(byte) 0x00, (byte) 0x88, (byte) 0x6e, (byte) 0x2f, (byte) 0x97, (byte) 0xac, (byte) 0xe4, (byte) 0x6e, (byte) 0x55, (byte) 0xba, (byte) 0x9d, (byte) 0xd7, (byte) 0x24, (byte) 0x25, (byte) 0x79, (byte) 0xf2, (byte) 0x99, (byte) 0x3b, (byte) 0x64, (byte) 0xe1, (byte) 0x6e, (byte) 0xf3, (byte) 0xdc, (byte) 0xab, (byte) 0x95, (byte) 0xaf, (byte) 0xd4, (byte) 0x97, (byte) 0x33, (byte) 0x3d, (byte) 0x8f, (byte) 0xa1, (byte) 0x2f};
byte[] My = {(byte) 0x5f, (byte) 0xf3, (byte) 0x55, (byte) 0x16, (byte) 0x3e, (byte) 0x43, (byte) 0xce, (byte) 0x22, (byte) 0x4e, (byte) 0x0b, (byte) 0x0e, (byte) 0x65, (byte) 0xff, (byte) 0x02, (byte) 0xac, (byte) 0x8e, (byte) 0x5c, (byte) 0x7b, (byte) 0xe0, (byte) 0x94, (byte) 0x19, (byte) 0xc7, (byte) 0x85, (byte) 0xe0, (byte) 0xca, (byte) 0x54, (byte) 0x7d, (byte) 0x55, (byte) 0xa1, (byte) 0x2e, (byte) 0x2d, (byte) 0x20};
M = Curve.createPoint(new BigInteger(Mx), new BigInteger(My));
byte[] Nx = {(byte) 0x00, (byte) 0xd8, (byte) 0xbb, (byte) 0xd6, (byte) 0xc6, (byte) 0x39, (byte) 0xc6, (byte) 0x29, (byte) 0x37, (byte) 0xb0, (byte) 0x4d, (byte) 0x99, (byte) 0x7f, (byte) 0x38, (byte) 0xc3, (byte) 0x77, (byte) 0x07, (byte) 0x19, (byte) 0xc6, (byte) 0x29, (byte) 0xd7, (byte) 0x01, (byte) 0x4d, (byte) 0x49, (byte) 0xa2, (byte) 0x4b, (byte) 0x4f, (byte) 0x98, (byte) 0xba, (byte) 0xa1, (byte) 0x29, (byte) 0x2b, (byte) 0x49};
byte[] Ny = {(byte) 0x07, (byte) 0xd6, (byte) 0x0a, (byte) 0xa6, (byte) 0xbf, (byte) 0xad, (byte) 0xe4, (byte) 0x50, (byte) 0x08, (byte) 0xa6, (byte) 0x36, (byte) 0x33, (byte) 0x7f, (byte) 0x51, (byte) 0x68, (byte) 0xc6, (byte) 0x4d, (byte) 0x9b, (byte) 0xd3, (byte) 0x60, (byte) 0x34, (byte) 0x80, (byte) 0x8c, (byte) 0xd5, (byte) 0x64, (byte) 0x49, (byte) 0x0b, (byte) 0x1e, (byte) 0x65, (byte) 0x6e, (byte) 0xdb, (byte) 0xe7};
N = Curve.createPoint(new BigInteger(Nx), new BigInteger(Ny));
byte[] ba_n = {(byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xbc, (byte) 0xe6, (byte) 0xfa, (byte) 0xad, (byte) 0xa7, (byte) 0x17, (byte) 0x9e, (byte) 0x84, (byte) 0xf3, (byte) 0xb9, (byte) 0xca, (byte) 0xc2, (byte) 0xfc, (byte) 0x63, (byte) 0x25, (byte) 0x51 - 1};
n = new BigInteger(ba_n);
}