Skip to content

Commit

Permalink
support sm2 key exchange
Browse files Browse the repository at this point in the history
  • Loading branch information
emmansun committed Apr 22, 2024
1 parent 1491629 commit 76b5cbc
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 3 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
**A Simple Pure JavaScript GM-Standards SM2/SM3/SM4 Implementation based on [sjcl](https://github.com/bitwiseshiftleft/sjcl).**

## SM2
- 加解密
- 签名、验签
- 密钥交换

目前实现:签名结果为**r || s**的拼接;加密结果为**C1 || C3 || C2**拼接,且C1没有点格式前缀字节。为了与其它系统兼容,需要进一步处理。具体使用方法,请参考[sm2_test.js](https://github.com/emmansun/sm4js/blob/master/src/sm2_test.js "sm2_test.js")

[gmsm-sm2js](https://github.com/emmansun/sm2js)互操作示例:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gmsm-sm4js",
"version": "0.3.0",
"version": "0.4.0",
"description": "Pure Javascript implementation of the SM2/SM3/SM4 cipher based on sjcl",
"keywords": [
"sm2",
Expand Down
45 changes: 45 additions & 0 deletions src/sm2.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ function bindSM2 (sjcl) {
return plaintext
},

/**
* SM2 Key Exchange, return the implicit signature
* @param {sjcl.ecc.sm2.secretKey} ephemeralPrivateKey Generated ephemeral private key
* @param {sjcl.ecc.sm2.publicKey} ephemeralPubKey Generated ephemeral public key
* @returns {sjcl.bn}
*/
implicitSig: function (ephemeralPrivateKey, ephemeralPubKey) {
const R = this._curve.r
return ephemeralPrivateKey._exponent.mul(ephemeralPubKey._avf()).add(this._exponent).mod(R)
},

getType: function () {
return 'sm2'
}
Expand Down Expand Up @@ -242,6 +253,40 @@ function bindSM2 (sjcl) {
return hash.finalize()
},

_avf: function () {
const bits = sjcl.bitArray.bitSlice(this._point.x.toBits(), 16 * 8)
bits[0] &= 0x7fffffff
bits[0] |= 0x80000000
return sjcl.bn.fromBits(bits)
},

/**
* SM2 Key Exchange, generate shared key material from shared secret key
* @param {Number} keyLen The required key length in bits
* @param {bitArray} za1 For initiator, this is own ZA; otherwise, it's peer's ZA
* @param {bitArray} za2 For initiator, this is peer's ZA; otherwise, it's own ZA
* @returns {bitArray} returns the agreed key material
*/
agreedKey: function (keyLen, za1, za2) {
const v = sjcl.bitArray.concat(sjcl.bitArray.concat(this._point.toBits(), za1), za2)
return sjcl.misc.kdf(keyLen, v)
},

/**
* SM2 Key Exchange, calculate the shared secret key
* @param {sjcl.ecc.sm2.publicKey} ephemeralPub The peer's ephemeral public key
* @param {sjcl.bn} t The implicitSig result
* @returns {sjcl.ecc.sm2.publicKey} returns the shared secret key
*/
sharedSecretKey: function (ephemeralPub, t) {
const SM2PublicKey = sjcl.ecc.sm2.publicKey
const x = ephemeralPub._avf()

const jacPub = ephemeralPub._point.toJac()
const p2 = jacPub.mult(x, ephemeralPub._point).add(this._point.toJac()).toAffine()
return new SM2PublicKey(p2.mult(t))
},

/**
* Calculate hash value of the data and uid.
*
Expand Down
139 changes: 139 additions & 0 deletions src/sm2_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,142 @@ test('encryption/decryption', function (t) {
t.equals(sjcl.codec.utf8String.fromBits(plaintext), 'send reinforcements, we\'re going to advance')
t.end()
})

test('SM2 Key Exchange test', function (t) {
const initiator = 'Alice'
const responder = 'Bob'
const keyLen = 8 * 48

const aliceKeys = sjcl.ecc.sm2.generateKeys(
sjcl.codec.hex.toBits('e04c3fd77408b56a648ad439f673511a2ae248def3bab26bdfc9cdbd0ae9607e')
) // share aliceKeys.pub to Bob in advance
const aliceEphemeralKeys = sjcl.ecc.sm2.generateKeys(undefined, 6, false) // share aliceEphemeralKeys.pub to Bob per key exchange

const bobKeys = sjcl.ecc.sm2.generateKeys(
sjcl.codec.hex.toBits('7a1136f60d2c5531447e5a3093078c2a505abf74f33aefed927ac0a5b27e7dd7')
) // share bobKeys.pub to Alice in advance
const bobEphemeralKeys = sjcl.ecc.sm2.generateKeys(undefined, 6, false) // share bobEphemeralKeys.pub to Alice per key exchange

const aliceKeyMaterial = bobKeys.pub.sharedSecretKey(
bobEphemeralKeys.pub,
aliceKeys.sec.implicitSig(
aliceEphemeralKeys.sec,
aliceEphemeralKeys.pub
)
).agreedKey(
keyLen,
aliceKeys.pub.za(initiator),
bobKeys.pub.za(responder)
)

const bobKeyMaterial = aliceKeys.pub.sharedSecretKey(
aliceEphemeralKeys.pub,
bobKeys.sec.implicitSig(
bobEphemeralKeys.sec,
bobEphemeralKeys.pub
)
).agreedKey(
keyLen,
aliceKeys.pub.za(initiator),
bobKeys.pub.za(responder)
)
t.equals(sjcl.codec.hex.fromBits(aliceKeyMaterial), sjcl.codec.hex.fromBits(bobKeyMaterial))
t.end()
})

test('SM2 Key Exchange test vector', function (t) {
const initiator = 'Alice'
const responder = 'Bob'
const keyLen = 8 * 48

const testVector = [
{
alicePriv:
'e04c3fd77408b56a648ad439f673511a2ae248def3bab26bdfc9cdbd0ae9607e',
aliceEphemeralPriv:
'6fe0bac5b09d3ab10f724638811c34464790520e4604e71e6cb0e5310623b5b1',
bobPriv:
'7a1136f60d2c5531447e5a3093078c2a505abf74f33aefed927ac0a5b27e7dd7',
bobEphemeralPriv:
'd0233bdbb0b8a7bfe1aab66132ef06fc4efaedd5d5000692bc21185242a31f6f',
sharedSecretKey:
'6ab5c9709277837cedc515730d04751ef81c71e81e0e52357a98cf41796ab560508da6e858b40c6264f17943037434174284a847f32c4f54104a98af5148d89f',
key: '1ad809ebc56ddda532020c352e1e60b121ebeb7b4e632db4dd90a362cf844f8bba85140e30984ddb581199bf5a9dda22'
},
{
alicePriv:
'cb5ac204b38d0e5c9fc38a467075986754018f7dbb7cbbc5b4c78d56a88a8ad8',
aliceEphemeralPriv:
'1681a66c02b67fdadfc53cba9b417b9499d0159435c86bb8760c3a03ae157539',
bobPriv:
'4f54b10e0d8e9e2fe5cc79893e37fd0fd990762d1372197ed92dde464b2773ef',
bobEphemeralPriv:
'a2fe43dea141e9acc88226eaba8908ad17e81376c92102cb8186e8fef61a8700',
sharedSecretKey:
'677d055355a1dcc9de4df00d3a80b6daa76bdf54ff7e0a3a6359fcd0c6f1e4b4697fffc41bbbcc3a28ea3aa1c6c380d1e92f142233afa4b430d02ab4cebc43b2',
key: '7a103ae61a30ed9df573a5febb35a9609cbed5681bcb98a8545351bf7d6824cc4635df5203712ea506e2e3c4ec9b12e7'
},
{
alicePriv:
'ee690a34a779ab48227a2f68b062a80f92e26d82835608dd01b7452f1e4fb296',
aliceEphemeralPriv:
'2046c6cee085665e9f3abeba41fd38e17a26c08f2f5e8f0e1007afc0bf6a2a5d',
bobPriv:
'8ef49ea427b13cc31151e1c96ae8a48cb7919063f2d342560fb7eaaffb93d8fe',
bobEphemeralPriv:
'9baf8d602e43fbae83fedb7368f98c969d378b8a647318f8cafb265296ae37de',
sharedSecretKey:
'f7e9f1447968b284ff43548fcec3752063ea386b48bfabb9baf2f9c1caa05c2fb12c2cca37326ce27e68f8cc6414c2554895519c28da1ca21e61890d0bc525c4',
key: 'b18e78e5072f301399dc1f4baf2956c0ed2d5f52f19abb1705131b0865b079031259ee6c629b4faed528bcfa1c5d2cbc'
}
]

for (let i = 0; i < testVector.length; i++) {
const aliceKeys = sjcl.ecc.sm2.generateKeys(
sjcl.codec.hex.toBits(testVector[i].alicePriv)
)
const aliceEphemeralKeys = sjcl.ecc.sm2.generateKeys(
sjcl.codec.hex.toBits(testVector[i].aliceEphemeralPriv)
)
const bobKeys = sjcl.ecc.sm2.generateKeys(
sjcl.codec.hex.toBits(testVector[i].bobPriv)
)
const bobEphemeralKeys = sjcl.ecc.sm2.generateKeys(
sjcl.codec.hex.toBits(testVector[i].bobEphemeralPriv)
)

const tA = aliceKeys.sec.implicitSig(
aliceEphemeralKeys.sec,
aliceEphemeralKeys.pub
)
const aliceSecretKey = bobKeys.pub.sharedSecretKey(
bobEphemeralKeys.pub,
tA
)
t.equals(aliceSecretKey.serialize().point, testVector[i].sharedSecretKey)
const aliceSharedKey = aliceSecretKey.agreedKey(
keyLen,
aliceKeys.pub.za(initiator),
bobKeys.pub.za(responder)
)
t.equals(sjcl.codec.hex.fromBits(aliceSharedKey), testVector[i].key)

const tB = bobKeys.sec.implicitSig(
bobEphemeralKeys.sec,
bobEphemeralKeys.pub
)
const bobSecretKey = aliceKeys.pub.sharedSecretKey(
aliceEphemeralKeys.pub,
tB
)
t.equals(bobSecretKey.serialize().point, testVector[i].sharedSecretKey)
const bobSharedKey = bobSecretKey.agreedKey(
keyLen,
aliceKeys.pub.za(initiator),
bobKeys.pub.za(responder)
)
t.equals(sjcl.codec.hex.fromBits(bobSharedKey), testVector[i].key)
}

t.end()
})

0 comments on commit 76b5cbc

Please sign in to comment.