sm2 加密解密通过

This commit is contained in:
cheney 2026-04-18 10:54:59 +08:00
parent 9f6df6bbd8
commit 0cb442eaa0
10 changed files with 1829 additions and 34 deletions

1064
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,12 +11,18 @@
"test": "jest"
},
"dependencies": {
"@peculiar/webcrypto": "^1.5.0",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.1",
"gm-crypto": "*",
"gmsm-sm2js": "^0.7.1",
"gmsm-sm3js": "^0.2.0",
"gmsm-sm4js": "^0.7.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sjcl-with-all": "^1.0.8"
"sjcl-with-all": "^1.0.8",
"sm-crypto": "^0.4.0",
"stream-browserify": "^3.0.0"
},
"devDependencies": {
"@types/jest": "^30.0.0",

View File

@ -3,6 +3,7 @@ import { VERSION } from './version'
import { sm3Digest, sm3Hmac } from './utils/algorithm/sm3'
import { sm4Encrypt, sm4Decrypt } from './utils/algorithm/sm4'
import { sm2GenerateKeyPair, sm2GetPublicKeyFromPrivateKey, sm2Encrypt, sm2Decrypt, sm2Sign, sm2Verify } from './utils/algorithm/sm2'
import { hexToUtf8, utf8ToHex } from './utils/convert'
function App() {
@ -52,6 +53,15 @@ function App() {
const [sm4Encrypted, setSm4Encrypted] = useState('')
const [sm4Decrypted, setSm4Decrypted] = useState('')
// SM2 状态
const [sm2PrivateKey, setSm2PrivateKey] = useState('')
const [sm2PublicKey, setSm2PublicKey] = useState('')
const [sm2Message, setSm2Message] = useState('')
const [sm2Encrypted, setSm2Encrypted] = useState('')
const [sm2Decrypted, setSm2Decrypted] = useState('')
const [sm2Signature, setSm2Signature] = useState('')
const [sm2VerifyResult, setSm2VerifyResult] = useState('')
// 转换工具状态
const [hexInput, setHexInput] = useState('')
const [utf8Input, setUtf8Input] = useState('')
@ -117,6 +127,67 @@ function App() {
alert('转换失败: ' + error)
}
}
// SM2 生成密钥对
const handleGenerateSm2KeyPair = () => {
try {
const keypair = sm2GenerateKeyPair()
setSm2PrivateKey(keypair.privateKey)
setSm2PublicKey(keypair.publicKey)
} catch (error) {
alert('生成密钥对失败: ' + (error instanceof Error ? error.message : error))
}
}
// SM2 从私钥计算公钥
const handleCalculateSm2PublicKey = () => {
try {
const publicKey = sm2GetPublicKeyFromPrivateKey(sm2PrivateKey)
setSm2PublicKey(publicKey)
} catch (error) {
alert('计算公钥失败: ' + (error instanceof Error ? error.message : error))
}
}
// SM2 加密
const handleEncryptSm2 = () => {
try {
const encrypted = sm2Encrypt(sm2Message, sm2PublicKey)
setSm2Encrypted(encrypted)
} catch (error) {
alert('加密失败: ' + (error instanceof Error ? error.message : error))
}
}
// SM2 解密
const handleDecryptSm2 = () => {
try {
const decrypted = sm2Decrypt(sm2Encrypted, sm2PrivateKey)
setSm2Decrypted(decrypted)
} catch (error) {
alert('解密失败: ' + (error instanceof Error ? error.message : error))
}
}
// SM2 签名
const handleSignSm2 = () => {
try {
const signature = sm2Sign(sm2Message, sm2PrivateKey)
setSm2Signature(signature)
} catch (error) {
alert('签名失败: ' + (error instanceof Error ? error.message : error))
}
}
// SM2 验证签名
const handleVerifySm2 = () => {
try {
const result = sm2Verify(sm2Message, sm2Signature, sm2PublicKey)
setSm2VerifyResult(result ? '验证成功' : '验证失败')
} catch (error) {
alert('验证签名失败: ' + (error instanceof Error ? error.message : error))
}
}
return (
<div className="app" style={{ maxWidth: '800px', margin: '0 auto', padding: '20px', fontFamily: 'Arial, sans-serif', backgroundColor: '#121212', color: '#e0e0e0', minHeight: '100vh' }}>
@ -125,6 +196,24 @@ function App() {
{/* 桌面端 Tab 选择 */}
{!isMobile && (
<div className="tabs" style={{ display: 'flex', borderBottom: '1px solid #333', marginBottom: '20px', backgroundColor: '#1e1e1e', borderRadius: '8px 8px 0 0', overflow: 'hidden' }}>
<button
className={`tab ${activeTab === 'sm2' ? 'active' : ''}`}
onClick={() => handleTabChange('sm2')}
style={{
padding: '12px 24px',
border: 'none',
background: activeTab === 'sm2' ? '#333' : '#1e1e1e',
cursor: 'pointer',
fontSize: '16px',
fontWeight: activeTab === 'sm2' ? 'bold' : 'normal',
color: activeTab === 'sm2' ? '#f59e0b' : '#e0e0e0',
outline: 'none',
transition: 'all 0.3s ease',
borderBottom: activeTab === 'sm2' ? '3px solid #f59e0b' : 'none'
}}
>
SM2
</button>
<button
className={`tab ${activeTab === 'sm3' ? 'active' : ''}`}
onClick={() => handleTabChange('sm3')}
@ -202,6 +291,7 @@ function App() {
cursor: 'pointer'
}}
>
<option value="sm2" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>SM2</option>
<option value="sm3" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>SM3</option>
<option value="sm4" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>SM4</option>
<option value="convert" style={{ background: '#1e1e1e', color: '#e0e0e0' }}></option>
@ -211,6 +301,267 @@ function App() {
{/* SM2 面板 */}
{activeTab === 'sm2' && (
<div className="panel" style={{ backgroundColor: '#1e1e1e', padding: '20px', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.3)' }}>
<h2 style={{ color: '#f59e0b', marginBottom: '20px' }}>SM2 </h2>
{/* 密钥对生成 */}
<div style={{ marginBottom: '20px', padding: '15px', backgroundColor: '#2d2d2d', borderRadius: '8px' }}>
<h3 style={{ color: '#e0e0e0', marginBottom: '10px' }}></h3>
<div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
<button
onClick={handleGenerateSm2KeyPair}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease'
}}
>
</button>
<button
onClick={handleCalculateSm2PublicKey}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease'
}}
>
</button>
</div>
<div className="form-group" style={{ marginBottom: '10px' }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}></label>
<textarea
value={sm2PrivateKey}
onChange={(e) => setSm2PrivateKey(e.target.value)}
placeholder="输入私钥 (hex) 或点击生成密钥对"
style={{
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#3d3d3d',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical',
minHeight: '80px'
}}
/>
</div>
<div className="form-group" style={{ marginBottom: '10px' }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}></label>
<textarea
value={sm2PublicKey}
readOnly
placeholder="公钥将显示在这里"
style={{
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#3d3d3d',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical',
minHeight: '80px'
}}
/>
</div>
</div>
{/* 加密/解密 */}
<div style={{ marginBottom: '20px', padding: '15px', backgroundColor: '#2d2d2d', borderRadius: '8px' }}>
<h3 style={{ color: '#e0e0e0', marginBottom: '10px' }}>/</h3>
<div className="form-group" style={{ marginBottom: '10px' }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}></label>
<textarea
value={sm2Message}
onChange={(e) => setSm2Message(e.target.value)}
placeholder="输入要加密的消息 (hex)"
style={{
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#3d3d3d',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical',
minHeight: '100px'
}}
/>
</div>
<div className="form-group" style={{ marginBottom: '10px' }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}></label>
<textarea
value={sm2Encrypted}
onChange={(e) => setSm2Encrypted(e.target.value)}
placeholder="输入加密结果 (hex)"
style={{
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#3d3d3d',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical',
minHeight: '100px'
}}
/>
</div>
<div className="form-group" style={{ marginBottom: '10px' }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}></label>
<textarea
value={sm2Decrypted}
readOnly
placeholder="解密结果将显示在这里"
style={{
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#3d3d3d',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical',
minHeight: '100px'
}}
/>
</div>
<div className="button-group" style={{ display: 'flex', gap: '10px' }}>
<button
onClick={handleEncryptSm2}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease',
flex: 1
}}
>
</button>
<button
onClick={handleDecryptSm2}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease',
flex: 1
}}
>
</button>
</div>
</div>
{/* 签名/验证 */}
<div style={{ marginBottom: '20px', padding: '15px', backgroundColor: '#2d2d2d', borderRadius: '8px' }}>
<h3 style={{ color: '#e0e0e0', marginBottom: '10px' }}>/</h3>
<div className="form-group" style={{ marginBottom: '10px' }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}></label>
<textarea
value={sm2Signature}
onChange={(e) => setSm2Signature(e.target.value)}
placeholder="输入签名 (hex)"
style={{
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#3d3d3d',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical',
minHeight: '100px'
}}
/>
</div>
<div className="form-group" style={{ marginBottom: '10px' }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}></label>
<div
style={{
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#3d3d3d',
color: sm2VerifyResult === '验证成功' ? '#4ade80' : sm2VerifyResult === '验证失败' ? '#f87171' : '#e0e0e0',
fontSize: '14px',
minHeight: '40px',
display: 'flex',
alignItems: 'center'
}}
>
{sm2VerifyResult || '验证结果将显示在这里'}
</div>
</div>
<div className="button-group" style={{ display: 'flex', gap: '10px' }}>
<button
onClick={handleSignSm2}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease',
flex: 1
}}
>
</button>
<button
onClick={handleVerifySm2}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '16px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease',
flex: 1
}}
>
</button>
</div>
</div>
</div>
)}
{/* SM3 面板 */}
{activeTab === 'sm3' && (
<div className="panel" style={{ backgroundColor: '#1e1e1e', padding: '20px', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.3)' }}>

View File

@ -1,4 +1,4 @@
import { generateSm2KeyPair, sm2Encrypt, sm2Decrypt } from '../../utils/algorithm/sm2'
import { sm2GenerateKeyPair, sm2Encrypt, sm2Decrypt } from '../../utils/algorithm/sm2'
// UTF8 转 Hex
const utf8ToHex = (utf8: string): string => {
@ -8,7 +8,7 @@ const utf8ToHex = (utf8: string): string => {
describe('SM2 算法测试', () => {
test('生成密钥对', () => {
const { privateKey, publicKey } = generateSm2KeyPair()
const { privateKey, publicKey } = sm2GenerateKeyPair()
expect(privateKey).toBeTruthy()
expect(publicKey).toBeTruthy()
expect(privateKey.length).toBeGreaterThan(0)
@ -16,7 +16,7 @@ describe('SM2 算法测试', () => {
})
test('加密和解密', () => {
const { privateKey, publicKey } = generateSm2KeyPair()
const { privateKey, publicKey } = sm2GenerateKeyPair()
const originalMessage = '测试消息'
const messageHex = utf8ToHex(originalMessage)

View File

@ -0,0 +1,288 @@
const test = require('tape')
const rs = require('jsrsasign')
const sm2 = require('../src/sm2')
const util = require('../src/util')
const publicKeyPemFromAliKmsForSign = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAERrsLH25zLm2LIo6tivZM9afLprSX
6TCKAmQJArAO7VOtZyW4PQwfaTsUIF7IXEFG4iI8bNuTQwMykUzLu2ypEA==
-----END PUBLIC KEY-----
`
const sm2PKIXPublicKeyHex = '3059301306072a8648ce3d020106082a811ccf5501822d03420004ef7db908af06082ef4a30e0ec28623371c106a53296a7b0e1a9b5717bd9cb81beb20d094aba685fd0f6a7ecc007ccf797ba634476326723b303d9dec873f440b'
const signatureHex = '30450220757984e0a063394ee0792b52172dd4273c05e2a66d734ff804a37b9ac639c098022100d9739a8d7a37fc88a1b4210998da489ad5b0dee1c8cb9097e532318aded5d204'
const csrFromAli = `-----BEGIN CERTIFICATE REQUEST-----
MIIBYjCCAQkCAQAwRzELMAkGA1UEBhMCQ04xEzARBgNVBAMMCkNhcmdvU21hcnQx
DzANBgNVBAcMBlpodWhhaTESMBAGA1UECAwJR3Vhbmdkb25nMFkwEwYHKoZIzj0C
AQYIKoEcz1UBgi0DQgAERrsLH25zLm2LIo6tivZM9afLprSX6TCKAmQJArAO7VOt
ZyW4PQwfaTsUIF7IXEFG4iI8bNuTQwMykUzLu2ypEKBgMC4GCSqGSIb3DQEJDjEh
MB8wHQYDVR0OBBYEFA3FO8vT+8qZBfGZa2TRhLRbme+9MC4GCSqGSIb3DQEJDjEh
MB8wHQYDVR0RBBYwFIESZW1tYW4uc3VuQGlxYXguY29tMAoGCCqBHM9VAYN1A0cA
MEQCIBQx6yv3rzfWCkKqDZQOfNKESQc6NtpQbeVvcxfBrciwAiAj78kkrF5R3g4l
bxIHjKZHc2sztHCXe7cseWGiLq0syg==
-----END CERTIFICATE REQUEST-----
`
// CA was from https://www.gmcert.org/
const CA_CERT = `-----BEGIN CERTIFICATE-----
MIICIzCCAcigAwIBAgIJAKun/ZLoSXfeMAoGCCqBHM9VAYN1MGcxCzAJBgNVBAYT
AkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdIYWlEaWFuMRMwEQYDVQQK
DApHTUNlcnQub3JnMR8wHQYDVQQDDBZHTUNlcnQgR00gUm9vdCBDQSAtIDAxMB4X
DTE5MTAyNDEyMzEzM1oXDTM5MDcxMTEyMzEzM1owZzELMAkGA1UEBhMCQ04xEDAO
BgNVBAgMB0JlaWppbmcxEDAOBgNVBAcMB0hhaURpYW4xEzARBgNVBAoMCkdNQ2Vy
dC5vcmcxHzAdBgNVBAMMFkdNQ2VydCBHTSBSb290IENBIC0gMDEwWTATBgcqhkjO
PQIBBggqgRzPVQGCLQNCAASXWWtv+ifV7dJHqPNXwcmioh/48Wg3IuI+o11nLEOD
zljxL2yMxoQM6xfNJHuqadXXNZv3D2rml5Pk0W/tmfHEo10wWzAdBgNVHQ4EFgQU
f1peOwCEWSoPmL6hDm85lUMQTQcwHwYDVR0jBBgwFoAUf1peOwCEWSoPmL6hDm85
lUMQTQcwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwCgYIKoEcz1UBg3UDSQAw
RgIhAJ7AZAC0i+4OyfxDuvPIg0I7ZtqL2kII2f1syaIW4C6iAiEAlHuUu0TMrOAr
sU47scL1B9BhyEh5tbEjsKLHia3K0YU=
-----END CERTIFICATE-----
`
// cert was generated from https://www.gmcert.org/
const cert = `-----BEGIN CERTIFICATE-----
MIICDTCCAbOgAwIBAgIJAOWoGwJCnVw5MAoGCCqBHM9VAYN1MGcxCzAJBgNVBAYT
AkNOMRAwDgYDVQQIDAdCZWlqaW5nMRAwDgYDVQQHDAdIYWlEaWFuMRMwEQYDVQQK
DApHTUNlcnQub3JnMR8wHQYDVQQDDBZHTUNlcnQgR00gUm9vdCBDQSAtIDAxMB4X
DTIxMDIyNDA3NTgxMloXDTIyMDIyNDA3NTgxMlowIjELMAkGA1UEBhMCQ04xEzAR
BgNVBAMMCkNhcmdvU21hcnQwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAATi93H1
6+sN4/e6ksqPb/yAaR5/ewgO0PVAtAqMXV3IIZsug/VgFrduCzE71PKHHKKrY3MA
d1pP8ozvDIGpoYJ8o4GMMIGJMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgeAMCwG
CWCGSAGG+EIBDQQfFh1HTUNlcnQub3JnIFNpZ25lZCBDZXJ0aWZpY2F0ZTAdBgNV
HQ4EFgQUPY0wMfEXn8wNhQTy7bL/dNJcA1UwHwYDVR0jBBgwFoAUf1peOwCEWSoP
mL6hDm85lUMQTQcwCgYIKoEcz1UBg3UDSAAwRQIgQsJ/kjgsc5cDavOvLvAOn2c9
u1EHM5QIWn58/xlMu1gCIQDk7Kp4A/c+W2lr93yFHiTPxwtKIz/nwtH4GRAcxeiM
iA==
-----END CERTIFICATE-----
`
const sm2PrivateKeyEncryptedPKCS8 = `
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIH2MGEGCSqGSIb3DQEFDTBUMDQGCSqGSIb3DQEFDDAnBBDa6ckWJNP3QBD7MIF8
4nVqAgEQAgEQMA0GCSqBHM9VAYMRAgUAMBwGCCqBHM9VAWgCBBDMUgr+5Y/XN2g9
mPGiISzGBIGQytwK98/ET4WrS0H7AsUri6FTqztrzAvgzFl3+s9AsaYtUlzE3EzE
x6RWxo8kpKO2yj0a/Jh9WZCD4XAcoZ9aMopiWlOdpXJr/iQlMGdirCYIoF37lHMc
jZHNffmk4ii7NxCfjrzpiFq4clYsNMXeSEnq1tuOEur4kYcjHYSIFc9bPG656a60
+SIJsJuPFi0f
-----END ENCRYPTED PRIVATE KEY-----`
const sm2PrivateKeyPlainPKCS8 = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgbFoKCy7tPL7D5PEl
K/4OKMUEoca/GZnuuwr57w+ObIWhRANCAASDVuZCpA69GNKbo1MvvZ87vujwJ8P2
85pbovhwNp+ZiJgfXv5V0cXN9sDvKwcIR6FPf99CcqjfCcRC8wWK+Uuh
-----END PRIVATE KEY-----`
const pkcs8SM2P256PrivateKeyHex = '308187020100301306072a8648ce3d020106082a811ccf5501822d046d306b0201010420b26da57ba53004ddcd387ad46a361b51b308481f2327d47fb10c5fb3a8c86b92a144034200040d5365bfdbdc564c5b0eda0a85ddbd753821a709de90efe0666ba2544766acf1100ac0484d166842011da5cd6139e53dedb99ce37cea9edf4941628066e861bf'
const sec1SM2PrivateKeyHex = '30770201010420857dd87970aab4328dad891c781e3b270742aa9cf5d3d3764efe77f6c3d6e33aa00a06082a811ccf5501822da14403420004ced963a5705a0490ff13dde893cbda6de61f41fcaf917a5b4007d30cdec46426bc39b9c18d15b2a68a64dc333f262e600b675856285b42296f24741ee6f562a0'
test('SM2 P-256 encrypt/decrypt local', function (t) {
const ec = new rs.ECDSA({ curve: sm2.getCurveName() })
const plainText = 'send reinforcements, we\'re going to advance'
const expected = '73656e64207265696e666f7263656d656e74732c20776527726520676f696e6720746f20616476616e6365'
const keypair = ec.generateKeyPairHex()
const ciphertext = sm2.encrypt(keypair.ecpubhex, plainText)
const asn1text = sm2.plainCiphertext2ASN1(ciphertext)
console.log('ciphertext=' + ciphertext)
console.log('asn.1 ciphertext=' + asn1text)
const ciphertext1 = sm2.asn1Ciphertext2Plain(asn1text)
t.equal(ciphertext1, ciphertext)
const result = sm2.decryptHex(keypair.ecprvhex, ciphertext)
t.equal(result, expected)
const result2 = sm2.decryptHex(keypair.ecprvhex, asn1text)
t.equal(result2, expected)
t.end()
})
test('SM2 P-256 encrypt, output hex asn.1 ciphertext', function (t) {
const ec = new rs.ECDSA({ curve: sm2.getCurveName() })
const plainText = 'send reinforcements, we\'re going to advance'
const expected = '73656e64207265696e666f7263656d656e74732c20776527726520676f696e6720746f20616476616e6365'
const keypair = ec.generateKeyPairHex()
const ciphertext = sm2.encrypt(keypair.ecpubhex, plainText, sm2.asn1EncrypterOptions())
const tag = ciphertext.substring(0, 2)
t.equal(tag, '30')
const result = sm2.decryptHex(keypair.ecprvhex, ciphertext)
t.equal(result, expected)
t.end()
})
test('SM2 P-256 sign/verify local', function (t) {
const ec = new rs.ECDSA({ curve: sm2.getCurveName() })
const keypair = ec.generateKeyPairHex()
const sig1 = sm2.createSM2Signature()
sig1.init({ curve: sm2.getCurveName(), d: keypair.ecprvhex })
sig1.updateString('emmansun')
const hSig = sig1.sign()
console.log('hSig=' + hSig)
const sig2 = sm2.createSM2Signature()
sig2.init({ curve: sm2.getCurveName(), xy: keypair.ecpubhex })
sig2.updateString('emmansun')
t.true(sig2.verify(hSig))
t.end()
})
test('SM2 P-256 sm2 specific sign/verify', function (t) {
const ec = new rs.ECDSA({ curve: sm2.getCurveName() })
const keypair = ec.generateKeyPairHex()
const sig1 = sm2.createSM2Signature()
sig1.init({ curve: sm2.getCurveName(), d: keypair.ecprvhex })
const hSig = sig1.sm2Sign('emmansun')
const hSig1 = sig1.sm2Sign('emmansun 1')
console.log('hSig=' + hSig)
console.log('hSig1=' + hSig1)
const sig2 = sm2.createSM2Signature()
sig2.init({ curve: sm2.getCurveName(), xy: keypair.ecpubhex })
t.true(sig2.sm2Verify(hSig, 'emmansun'))
t.true(sig2.sm2Verify(hSig1, 'emmansun 1'))
t.end()
})
test('NIST P-256 sign/verify local', function (t) {
const ec = new rs.ECDSA({ curve: sm2.getCurveName() })
const keypair = ec.generateKeyPairHex()
const sig1 = new sm2.Signature({ alg: 'SHA256withECDSA' })
sig1.init({ curve: sm2.getCurveName(), d: keypair.ecprvhex })
sig1.updateString('emmansun')
const hSig = sig1.sign()
console.log('hSig=' + hSig)
const sig2 = new sm2.Signature({ alg: 'SHA256withECDSA' })
sig2.init({ curve: sm2.getCurveName(), xy: keypair.ecpubhex })
sig2.updateString('emmansun')
t.true(sig2.verify(hSig))
t.end()
})
test('SM2 parse public key pem, verify signature, both from ali KMS', function (t) {
const sig = sm2.createSM2Signature()
sig.init(publicKeyPemFromAliKmsForSign)
t.equal(sig.pubKey.curveName, sm2.getCurveName())
t.true(sig.verifyWithMessageHash('66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0', signatureHex))
t.end()
})
test('SM2 calculate ZA', function (t) {
const sig = sm2.createSM2Signature()
sig.init(publicKeyPemFromAliKmsForSign)
const za = util.toHex(sig.pubKey.calculateZA())
t.equal(za, '17e7fc071f1418200aeead3c5118a2f18381431d92b808a3bd1ba2d8270c2914')
t.end()
})
test('SM2 parse CSR from ALI KMS', function (t) {
const result = rs.asn1.csr.CSRUtil.getParam(csrFromAli)
t.equal(result.sigalg, sm2.getSignAlg())
t.end()
})
test('SM2 gen CSR', function (t) {
const kp = rs.KEYUTIL.generateKeypair('EC', sm2.getCurveName())
const prvKey = kp.prvKeyObj
const pubKey = kp.pubKeyObj
const csr = rs.asn1.csr.CSRUtil.newCSRPEM({
subject: { str: '/C=US/O=TEST' },
sbjpubkey: pubKey,
sigalg: sm2.getSignAlg(),
sbjprvkey: prvKey
})
console.log(csr)
const result = rs.asn1.csr.CSRUtil.getParam(csr)
console.log(JSON.stringify(result))
t.end()
})
test('SM2 read cert', function (t) {
const x = sm2.createX509()
x.readCertPEM(cert)
t.equal(x.getSignatureAlgorithmField(), sm2.getSignAlg())
t.true(x.verifySignature(rs.KEYUTIL.getKey(CA_CERT)))
t.end()
})
test('Parse PKCS8 encrypted SM2 private key', function (t) {
const key = rs.KEYUTIL.getKeyFromEncryptedPKCS8PEM(sm2PrivateKeyEncryptedPKCS8, 'Password1')
t.equal(key.curveName, 'sm2p256v1')
t.equal(key.prvKeyHex, '6c5a0a0b2eed3cbec3e4f1252bfe0e28c504a1c6bf1999eebb0af9ef0f8e6c85')
t.equal(key.pubKeyHex, '048356e642a40ebd18d29ba3532fbd9f3bbee8f027c3f6f39a5ba2f870369f9988981f5efe55d1c5cdf6c0ef2b070847a14f7fdf4272a8df09c442f3058af94ba1')
t.end()
})
test('Parse PKCS8 unencrypted SM2 private key', function (t) {
const key = rs.KEYUTIL.getKeyFromPlainPrivatePKCS8PEM(sm2PrivateKeyPlainPKCS8)
t.equal(key.curveName, 'sm2p256v1')
t.equal(key.prvKeyHex, '6c5a0a0b2eed3cbec3e4f1252bfe0e28c504a1c6bf1999eebb0af9ef0f8e6c85')
t.equal(key.pubKeyHex, '048356e642a40ebd18d29ba3532fbd9f3bbee8f027c3f6f39a5ba2f870369f9988981f5efe55d1c5cdf6c0ef2b070847a14f7fdf4272a8df09c442f3058af94ba1')
t.end()
})
test('Parse PKCS8 unencrypted SM2 private key with Signature', function (t) {
const sig1 = sm2.createSM2Signature()
sig1.init(sm2PrivateKeyPlainPKCS8)
const hSig = sig1.sm2Sign('emmansun')
console.log('hSig=' + hSig)
const sig2 = sm2.createSM2Signature()
sig2.init({ curve: sm2.getCurveName(), xy: sig1.prvKey.pubKeyHex })
t.true(sig2.sm2Verify(hSig, 'emmansun'))
t.end()
})
test('Parse PKCS8 encrypted SM2 private key with Signature', function (t) {
const sig1 = sm2.createSM2Signature()
sig1.init(sm2PrivateKeyEncryptedPKCS8, 'Password1')
const hSig = sig1.sm2Sign('emmansun')
console.log('hSig=' + hSig)
const sig2 = sm2.createSM2Signature()
sig2.init({ curve: sm2.getCurveName(), xy: sig1.prvKey.pubKeyHex })
t.true(sig2.sm2Verify(hSig, 'emmansun'))
t.end()
})
test('Parse PKCS8 unencrypted SM2 private key (hex) with Signature', function (t) {
const sig1 = sm2.createSM2Signature()
sig1.init(pkcs8SM2P256PrivateKeyHex, undefined, 'pkcs8prv')
const hSig = sig1.sm2Sign('emmansun')
console.log('hSig=' + hSig)
const sig2 = sm2.createSM2Signature()
sig2.init({ curve: sm2.getCurveName(), xy: sig1.prvKey.pubKeyHex })
t.true(sig2.sm2Verify(hSig, 'emmansun'))
t.end()
})
test('Parse SEC1 SM2 private key (hex) with Signature', function (t) {
const sig1 = sm2.createSM2Signature()
sig1.init(sec1SM2PrivateKeyHex, undefined, 'pkcs5prv')
const hSig = sig1.sm2Sign('emmansun')
console.log('hSig=' + hSig)
const sig2 = sm2.createSM2Signature()
sig2.init({ curve: sm2.getCurveName(), xy: sig1.prvKey.pubKeyHex })
t.true(sig2.sm2Verify(hSig, 'emmansun'))
t.end()
})
test('Parse SM2 PKIX public key (hex) with Signature', function (t) {
const sig1 = sm2.createSM2Signature()
sig1.init(sm2PKIXPublicKeyHex, undefined, 'pkcs8pub')
t.equals(sig1.pubKey.pubKeyHex, '04ef7db908af06082ef4a30e0ec28623371c106a53296a7b0e1a9b5717bd9cb81beb20d094aba685fd0f6a7ecc007ccf797ba634476326723b303d9dec873f440b')
t.end()
})

7
src/types/gmsm-sm2js.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
declare module 'gmsm-sm2js' {
export function generateKeyPairHex(privateKey?: string): { privateKey: string; publicKey: string };
export function encrypt(publicKey: string, message: string): string;
export function decrypt(privateKey: string, ciphertext: string): string;
export function sign(privateKey: string, message: string): string;
export function verify(publicKey: string, message: string, signature: string): boolean;
}

23
src/types/sm-crypto.d.ts vendored Normal file
View File

@ -0,0 +1,23 @@
declare module 'sm-crypto' {
export interface SM2KeyPair {
privateKey: string;
publicKey: string;
}
export const sm2: {
generateKeyPairHex: (privateKey?: string) => SM2KeyPair;
doEncrypt: (message: string, publicKey: string) => string;
doDecrypt: (ciphertext: string, privateKey: string) => string;
doSign: (message: string, privateKey: string) => string;
doVerify: (message: string, signature: string, publicKey: string) => boolean;
};
export const sm3: {
digest: (message: string) => string;
};
export const sm4: {
encrypt: (message: string, key: string, options?: any) => string;
decrypt: (ciphertext: string, key: string, options?: any) => string;
};
}

View File

@ -1,16 +1,29 @@
import { SM2 } from 'gm-crypto'
import { isValidHex, hexToArrayBuffer } from '../convert/hex'
// 验证是否为有效的 hex 字符串
const isValidHex = (hex: string): boolean => {
return /^[0-9a-fA-F]*$/.test(hex) && hex.length % 2 === 0
// 导入 sm-crypto 库,它在浏览器环境中可用
import { sm2 } from 'sm-crypto'
// SM2 密钥对生成
export const sm2GenerateKeyPair = (): { privateKey: string; publicKey: string } => {
const keypair = sm2.generateKeyPairHex()
return {
privateKey: keypair.privateKey,
publicKey: keypair.publicKey
}
}
// 生成 SM2 密钥对
export const generateSm2KeyPair = () => {
return SM2.generateKeyPair()
// SM2 从私钥计算公钥
export const sm2GetPublicKeyFromPrivateKey = (privateKey: string): string => {
// 验证私钥是否为有效的 hex 字符串
if (!isValidHex(privateKey)) {
throw new Error('Invalid private key: must be hex string')
}
const keypair = sm2.generateKeyPairHex(privateKey)
return keypair.publicKey
}
// SM2 公钥加密
// SM2 加密
export const sm2Encrypt = (message: string, publicKey: string): string => {
// 验证消息是否为有效的 hex 字符串
if (!isValidHex(message)) {
@ -21,23 +34,78 @@ export const sm2Encrypt = (message: string, publicKey: string): string => {
throw new Error('Invalid public key: must be hex string')
}
// 使用 hex 输入模式加密
const encrypted = SM2.encrypt(message, publicKey, { inputEncoding: 'hex' })
return typeof encrypted === 'string' ? encrypted : Buffer.from(encrypted).toString('hex')
// 将 hex 字符串转换为字节数组
const messageBytes = new Uint8Array(hexToArrayBuffer(message))
// 将字节数组转换为字符串
const messageString = new TextDecoder().decode(messageBytes)
// 加密
const encrypted = sm2.doEncrypt(messageString, publicKey)
return encrypted
}
// SM2 私钥解密
export const sm2Decrypt = (encrypted: string, privateKey: string): string => {
// 验证加密数据是否为有效的 hex 字符串
if (!isValidHex(encrypted)) {
throw new Error('Invalid encrypted data: must be hex string')
// SM2 解密
export const sm2Decrypt = (ciphertext: string, privateKey: string): string => {
// 验证密文是否为有效的 hex 字符串
if (!isValidHex(ciphertext)) {
throw new Error('Invalid ciphertext: must be hex string')
}
// 验证私钥是否为有效的 hex 字符串
if (!isValidHex(privateKey)) {
throw new Error('Invalid private key: must be hex string')
}
// 使用 hex 输入模式解密
const decrypted = SM2.decrypt(encrypted, privateKey, { inputEncoding: 'hex' })
return typeof decrypted === 'string' ? decrypted : new TextDecoder('utf-8').decode(decrypted)
// 解密
const decrypted = sm2.doDecrypt(ciphertext, privateKey)
// 将解密结果转换为 hex 字符串
const decryptedBytes = new TextEncoder().encode(decrypted)
return Array.from(decryptedBytes)
.map(byte => byte.toString(16).padStart(2, '0'))
.join('')
}
// SM2 签名
export const sm2Sign = (message: string, privateKey: string): string => {
// 验证消息是否为有效的 hex 字符串
if (!isValidHex(message)) {
throw new Error('Invalid message: must be hex string')
}
// 验证私钥是否为有效的 hex 字符串
if (!isValidHex(privateKey)) {
throw new Error('Invalid private key: must be hex string')
}
// 将 hex 字符串转换为字节数组
const messageBytes = new Uint8Array(hexToArrayBuffer(message))
// 将字节数组转换为字符串
const messageString = new TextDecoder().decode(messageBytes)
// 签名
const signature = sm2.doSign(messageString, privateKey)
return signature
}
// SM2 验证签名
export const sm2Verify = (message: string, signature: string, publicKey: string): boolean => {
// 验证消息是否为有效的 hex 字符串
if (!isValidHex(message)) {
throw new Error('Invalid message: must be hex string')
}
// 验证签名是否为有效的 hex 字符串
if (!isValidHex(signature)) {
throw new Error('Invalid signature: must be hex string')
}
// 验证公钥是否为有效的 hex 字符串
if (!isValidHex(publicKey)) {
throw new Error('Invalid public key: must be hex string')
}
// 将 hex 字符串转换为字节数组
const messageBytes = new Uint8Array(hexToArrayBuffer(message))
// 将字节数组转换为字符串
const messageString = new TextDecoder().decode(messageBytes)
// 验证签名
const result = sm2.doVerify(messageString, signature, publicKey)
return result
}

View File

@ -1 +1 @@
export const VERSION = "V1.0-20260417024927";
export const VERSION = "V1.0-20260418023820";

View File

@ -4,5 +4,11 @@ import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
base: './'
resolve: {
alias: {
crypto: 'crypto-browserify',
stream: 'stream-browserify',
buffer: 'buffer'
}
}
})