Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88acc24e86 | ||
|
|
b757597db9 | ||
|
|
20c27b2e5b |
Generated
+20
-2
@@ -9,12 +9,14 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@peculiar/webcrypto": "^1.5.0",
|
"@peculiar/webcrypto": "^1.5.0",
|
||||||
|
"@types/node-forge": "^1.3.14",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"crypto-browserify": "^3.12.1",
|
"crypto-browserify": "^3.12.1",
|
||||||
"gm-crypto": "*",
|
"gm-crypto": "*",
|
||||||
"gmsm-sm2js": "^0.7.1",
|
"gmsm-sm2js": "^0.7.1",
|
||||||
"gmsm-sm3js": "^0.2.0",
|
"gmsm-sm3js": "^0.2.0",
|
||||||
"gmsm-sm4js": "^0.7.0",
|
"gmsm-sm4js": "^0.7.0",
|
||||||
|
"node-forge": "^1.4.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"sjcl-with-all": "^1.0.8",
|
"sjcl-with-all": "^1.0.8",
|
||||||
@@ -2417,12 +2419,20 @@
|
|||||||
"version": "25.6.0",
|
"version": "25.6.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.6.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.6.0.tgz",
|
||||||
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.19.0"
|
"undici-types": "~7.19.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node-forge": {
|
||||||
|
"version": "1.3.14",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/node-forge/-/node-forge-1.3.14.tgz",
|
||||||
|
"integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
@@ -6386,6 +6396,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-forge": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
|
||||||
|
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-int64": {
|
"node_modules/node-int64": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz",
|
||||||
@@ -7792,7 +7811,6 @@
|
|||||||
"version": "7.19.2",
|
"version": "7.19.2",
|
||||||
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.19.2.tgz",
|
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.19.2.tgz",
|
||||||
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unrs-resolver": {
|
"node_modules/unrs-resolver": {
|
||||||
|
|||||||
@@ -12,12 +12,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@peculiar/webcrypto": "^1.5.0",
|
"@peculiar/webcrypto": "^1.5.0",
|
||||||
|
"@types/node-forge": "^1.3.14",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"crypto-browserify": "^3.12.1",
|
"crypto-browserify": "^3.12.1",
|
||||||
"gm-crypto": "*",
|
"gm-crypto": "*",
|
||||||
"gmsm-sm2js": "^0.7.1",
|
"gmsm-sm2js": "^0.7.1",
|
||||||
"gmsm-sm3js": "^0.2.0",
|
"gmsm-sm3js": "^0.2.0",
|
||||||
"gmsm-sm4js": "^0.7.0",
|
"gmsm-sm4js": "^0.7.0",
|
||||||
|
"node-forge": "^1.4.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"sjcl-with-all": "^1.0.8",
|
"sjcl-with-all": "^1.0.8",
|
||||||
|
|||||||
+446
-168
@@ -4,7 +4,8 @@ import { VERSION } from './version'
|
|||||||
import { sm3Digest, sm3Hmac } from './utils/algorithm/sm3'
|
import { sm3Digest, sm3Hmac } from './utils/algorithm/sm3'
|
||||||
import { sm4Encrypt, sm4Decrypt } from './utils/algorithm/sm4'
|
import { sm4Encrypt, sm4Decrypt } from './utils/algorithm/sm4'
|
||||||
import { sm2GenerateKeyPair, sm2GetPublicKeyFromPrivateKey, sm2Encrypt, sm2Decrypt, sm2Sign, sm2Verify } from './utils/algorithm/sm2'
|
import { sm2GenerateKeyPair, sm2GetPublicKeyFromPrivateKey, sm2Encrypt, sm2Decrypt, sm2Sign, sm2Verify } from './utils/algorithm/sm2'
|
||||||
import { hexToUtf8, utf8ToHex } from './utils/convert'
|
import { parseSm2Cert, parseCertFromFile } from './utils/algorithm/cert'
|
||||||
|
import { hexToUtf8, utf8ToHex, hexToBase64, base64ToHex } from './utils/convert'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [activeTab, setActiveTab] = useState('sm3')
|
const [activeTab, setActiveTab] = useState('sm3')
|
||||||
@@ -59,12 +60,18 @@ function App() {
|
|||||||
const [sm2Message, setSm2Message] = useState('')
|
const [sm2Message, setSm2Message] = useState('')
|
||||||
const [sm2Encrypted, setSm2Encrypted] = useState('')
|
const [sm2Encrypted, setSm2Encrypted] = useState('')
|
||||||
const [sm2Decrypted, setSm2Decrypted] = useState('')
|
const [sm2Decrypted, setSm2Decrypted] = useState('')
|
||||||
|
const [sm2SignMessage, setSm2SignMessage] = useState('')
|
||||||
const [sm2Signature, setSm2Signature] = useState('')
|
const [sm2Signature, setSm2Signature] = useState('')
|
||||||
const [sm2VerifyResult, setSm2VerifyResult] = useState('')
|
const [sm2VerifyResult, setSm2VerifyResult] = useState('')
|
||||||
|
const [sm2ActiveTab, setSm2ActiveTab] = useState('encrypt')
|
||||||
|
|
||||||
// 转换工具状态
|
// 转换工具状态
|
||||||
const [hexInput, setHexInput] = useState('')
|
const [hexInput, setHexInput] = useState('')
|
||||||
const [utf8Input, setUtf8Input] = useState('')
|
const [utf8Input, setUtf8Input] = useState('')
|
||||||
|
const [base64Input, setBase64Input] = useState('')
|
||||||
|
|
||||||
|
// 证书工具状态
|
||||||
|
const [certInfo, setCertInfo] = useState<any>(null)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -128,6 +135,53 @@ function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hex 转 Base64
|
||||||
|
const handleHexToBase64 = () => {
|
||||||
|
try {
|
||||||
|
const base64 = hexToBase64(hexInput)
|
||||||
|
setBase64Input(base64)
|
||||||
|
} catch (error) {
|
||||||
|
alert('转换失败: ' + error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 转 Hex
|
||||||
|
const handleBase64ToHex = () => {
|
||||||
|
try {
|
||||||
|
const hex = base64ToHex(base64Input)
|
||||||
|
setHexInput(hex)
|
||||||
|
} catch (error) {
|
||||||
|
alert('转换失败: ' + error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理证书文件上传
|
||||||
|
const handleCertFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0]
|
||||||
|
if (!file) return
|
||||||
|
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = (event) => {
|
||||||
|
const result = event.target?.result as ArrayBuffer
|
||||||
|
if (result) {
|
||||||
|
const base64Cert = parseCertFromFile(result)
|
||||||
|
handleParseCert(base64Cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.readAsArrayBuffer(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理证书解析
|
||||||
|
const handleParseCert = (certContent: string) => {
|
||||||
|
try {
|
||||||
|
const info = parseSm2Cert(certContent)
|
||||||
|
setCertInfo(info)
|
||||||
|
} catch (error) {
|
||||||
|
alert('证书解析失败: ' + (error instanceof Error ? error.message : error))
|
||||||
|
setCertInfo(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SM2 生成密钥对
|
// SM2 生成密钥对
|
||||||
const handleGenerateSm2KeyPair = () => {
|
const handleGenerateSm2KeyPair = () => {
|
||||||
try {
|
try {
|
||||||
@@ -172,8 +226,8 @@ function App() {
|
|||||||
// SM2 签名
|
// SM2 签名
|
||||||
const handleSignSm2 = () => {
|
const handleSignSm2 = () => {
|
||||||
try {
|
try {
|
||||||
console.log('开始签名,消息:', sm2Message, '私钥:', sm2PrivateKey)
|
console.log('开始签名,消息:', sm2SignMessage, '私钥:', sm2PrivateKey)
|
||||||
const signature = sm2Sign(sm2Message, sm2PrivateKey)
|
const signature = sm2Sign(sm2SignMessage, sm2PrivateKey)
|
||||||
console.log('签名成功,结果:', signature)
|
console.log('签名成功,结果:', signature)
|
||||||
setSm2Signature(signature)
|
setSm2Signature(signature)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -185,8 +239,8 @@ function App() {
|
|||||||
// SM2 验证签名
|
// SM2 验证签名
|
||||||
const handleVerifySm2 = () => {
|
const handleVerifySm2 = () => {
|
||||||
try {
|
try {
|
||||||
console.log('开始验签,消息:', sm2Message, '签名:', sm2Signature, '公钥:', sm2PublicKey)
|
console.log('开始验签,消息:', sm2SignMessage, '签名:', sm2Signature, '公钥:', sm2PublicKey)
|
||||||
const result = sm2Verify(sm2Message, sm2Signature, sm2PublicKey)
|
const result = sm2Verify(sm2SignMessage, sm2Signature, sm2PublicKey)
|
||||||
console.log('验签成功,结果:', result)
|
console.log('验签成功,结果:', result)
|
||||||
setSm2VerifyResult(result ? '验证成功' : '验证失败')
|
setSm2VerifyResult(result ? '验证成功' : '验证失败')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -284,6 +338,24 @@ function App() {
|
|||||||
>
|
>
|
||||||
转换工具
|
转换工具
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className={`tab ${activeTab === 'cert' ? 'active' : ''}`}
|
||||||
|
onClick={() => handleTabChange('cert')}
|
||||||
|
style={{
|
||||||
|
padding: '12px 24px',
|
||||||
|
border: 'none',
|
||||||
|
background: activeTab === 'cert' ? '#333' : '#1e1e1e',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: activeTab === 'cert' ? 'bold' : 'normal',
|
||||||
|
color: activeTab === 'cert' ? '#f59e0b' : '#e0e0e0',
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
borderBottom: activeTab === 'cert' ? '3px solid #f59e0b' : 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
证书工具
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -311,6 +383,7 @@ function App() {
|
|||||||
<option value="sm3" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>SM3</option>
|
<option value="sm3" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>SM3</option>
|
||||||
<option value="sm4" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>SM4</option>
|
<option value="sm4" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>SM4</option>
|
||||||
<option value="convert" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>转换工具</option>
|
<option value="convert" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>转换工具</option>
|
||||||
|
<option value="cert" style={{ background: '#1e1e1e', color: '#e0e0e0' }}>证书工具</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -382,8 +455,8 @@ function App() {
|
|||||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>公钥</label>
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>公钥</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={sm2PublicKey}
|
value={sm2PublicKey}
|
||||||
readOnly
|
onChange={(e) => setSm2PublicKey(e.target.value)}
|
||||||
placeholder="公钥将显示在这里"
|
placeholder="输入公钥 (hex) 或点击生成密钥对"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '10px',
|
padding: '10px',
|
||||||
@@ -399,181 +472,246 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 加密/解密 */}
|
{/* SM2 功能选项卡 */}
|
||||||
<div style={{ marginBottom: '20px', padding: '15px', backgroundColor: '#2d2d2d', borderRadius: '8px' }}>
|
<div style={{ marginBottom: '20px' }}>
|
||||||
<h3 style={{ color: '#e0e0e0', marginBottom: '10px' }}>加密/解密</h3>
|
<div style={{ display: 'flex', borderBottom: '1px solid #333', marginBottom: '20px', backgroundColor: '#2d2d2d', borderRadius: '8px 8px 0 0', overflow: 'hidden' }}>
|
||||||
<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
|
<button
|
||||||
onClick={handleEncryptSm2}
|
className={`tab ${sm2ActiveTab === 'encrypt' ? 'active' : ''}`}
|
||||||
|
onClick={() => setSm2ActiveTab('encrypt')}
|
||||||
style={{
|
style={{
|
||||||
padding: '10px 20px',
|
padding: '12px 24px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '4px',
|
background: sm2ActiveTab === 'encrypt' ? '#333' : '#2d2d2d',
|
||||||
background: '#f59e0b',
|
|
||||||
color: '#121212',
|
|
||||||
fontSize: '16px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'background 0.3s ease',
|
fontSize: '16px',
|
||||||
flex: 1
|
fontWeight: sm2ActiveTab === 'encrypt' ? 'bold' : 'normal',
|
||||||
|
color: sm2ActiveTab === 'encrypt' ? '#f59e0b' : '#e0e0e0',
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
borderBottom: sm2ActiveTab === 'encrypt' ? '3px solid #f59e0b' : 'none'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
加密
|
加密/解密
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleDecryptSm2}
|
className={`tab ${sm2ActiveTab === 'sign' ? 'active' : ''}`}
|
||||||
|
onClick={() => setSm2ActiveTab('sign')}
|
||||||
style={{
|
style={{
|
||||||
padding: '10px 20px',
|
padding: '12px 24px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '4px',
|
background: sm2ActiveTab === 'sign' ? '#333' : '#2d2d2d',
|
||||||
background: '#f59e0b',
|
|
||||||
color: '#121212',
|
|
||||||
fontSize: '16px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'background 0.3s ease',
|
fontSize: '16px',
|
||||||
flex: 1
|
fontWeight: sm2ActiveTab === 'sign' ? 'bold' : 'normal',
|
||||||
|
color: sm2ActiveTab === 'sign' ? '#f59e0b' : '#e0e0e0',
|
||||||
|
outline: 'none',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
borderBottom: sm2ActiveTab === 'sign' ? '3px solid #f59e0b' : 'none'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
解密
|
签名/验证
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 签名/验证 */}
|
{/* 加密/解密 */}
|
||||||
<div style={{ marginBottom: '20px', padding: '15px', backgroundColor: '#2d2d2d', borderRadius: '8px' }}>
|
{sm2ActiveTab === 'encrypt' && (
|
||||||
<h3 style={{ color: '#e0e0e0', marginBottom: '10px' }}>签名/验证</h3>
|
<div style={{ padding: '15px', backgroundColor: '#2d2d2d', borderRadius: '8px' }}>
|
||||||
<div className="form-group" style={{ marginBottom: '10px' }}>
|
<h3 style={{ color: '#e0e0e0', marginBottom: '10px' }}>加密/解密</h3>
|
||||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>签名</label>
|
<div className="form-group" style={{ marginBottom: '10px' }}>
|
||||||
<textarea
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>消息</label>
|
||||||
value={sm2Signature}
|
<textarea
|
||||||
onChange={(e) => setSm2Signature(e.target.value)}
|
value={sm2Message}
|
||||||
placeholder="输入签名 (hex)"
|
onChange={(e) => setSm2Message(e.target.value)}
|
||||||
style={{
|
placeholder="输入要加密的消息 (hex)"
|
||||||
width: '100%',
|
style={{
|
||||||
padding: '10px',
|
width: '100%',
|
||||||
border: '1px solid #333',
|
padding: '10px',
|
||||||
borderRadius: '4px',
|
border: '1px solid #333',
|
||||||
backgroundColor: '#3d3d3d',
|
borderRadius: '4px',
|
||||||
color: '#e0e0e0',
|
backgroundColor: '#3d3d3d',
|
||||||
fontSize: '14px',
|
color: '#e0e0e0',
|
||||||
resize: 'vertical',
|
fontSize: '14px',
|
||||||
minHeight: '100px'
|
resize: 'vertical',
|
||||||
}}
|
minHeight: '100px'
|
||||||
/>
|
}}
|
||||||
</div>
|
/>
|
||||||
<div className="form-group" style={{ marginBottom: '10px' }}>
|
</div>
|
||||||
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>验证结果</label>
|
<div className="form-group" style={{ marginBottom: '10px' }}>
|
||||||
<div
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>加密结果</label>
|
||||||
style={{
|
<textarea
|
||||||
width: '100%',
|
value={sm2Encrypted}
|
||||||
padding: '10px',
|
onChange={(e) => setSm2Encrypted(e.target.value)}
|
||||||
border: '1px solid #333',
|
placeholder="输入加密结果 (hex)"
|
||||||
borderRadius: '4px',
|
style={{
|
||||||
backgroundColor: '#3d3d3d',
|
width: '100%',
|
||||||
color: sm2VerifyResult === '验证成功' ? '#4ade80' : sm2VerifyResult === '验证失败' ? '#f87171' : '#e0e0e0',
|
padding: '10px',
|
||||||
fontSize: '14px',
|
border: '1px solid #333',
|
||||||
minHeight: '40px',
|
borderRadius: '4px',
|
||||||
display: 'flex',
|
backgroundColor: '#3d3d3d',
|
||||||
alignItems: 'center'
|
color: '#e0e0e0',
|
||||||
}}
|
fontSize: '14px',
|
||||||
>
|
resize: 'vertical',
|
||||||
{sm2VerifyResult || '验证结果将显示在这里'}
|
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>
|
||||||
</div>
|
)}
|
||||||
<div className="button-group" style={{ display: 'flex', gap: '10px' }}>
|
|
||||||
<button
|
{/* 签名/验证 */}
|
||||||
onClick={handleSignSm2}
|
{sm2ActiveTab === 'sign' && (
|
||||||
style={{
|
<div style={{ padding: '15px', backgroundColor: '#2d2d2d', borderRadius: '8px' }}>
|
||||||
padding: '10px 20px',
|
<h3 style={{ color: '#e0e0e0', marginBottom: '10px' }}>签名/验证</h3>
|
||||||
border: 'none',
|
<div className="form-group" style={{ marginBottom: '10px' }}>
|
||||||
borderRadius: '4px',
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>消息</label>
|
||||||
background: '#f59e0b',
|
<textarea
|
||||||
color: '#121212',
|
value={sm2SignMessage}
|
||||||
fontSize: '16px',
|
onChange={(e) => setSm2SignMessage(e.target.value)}
|
||||||
fontWeight: 'bold',
|
placeholder="输入要签名的消息 (hex)"
|
||||||
cursor: 'pointer',
|
style={{
|
||||||
transition: 'background 0.3s ease',
|
width: '100%',
|
||||||
flex: 1
|
padding: '10px',
|
||||||
}}
|
border: '1px solid #333',
|
||||||
>
|
borderRadius: '4px',
|
||||||
签名
|
backgroundColor: '#3d3d3d',
|
||||||
</button>
|
color: '#e0e0e0',
|
||||||
<button
|
fontSize: '14px',
|
||||||
onClick={handleVerifySm2}
|
resize: 'vertical',
|
||||||
style={{
|
minHeight: '100px'
|
||||||
padding: '10px 20px',
|
}}
|
||||||
border: 'none',
|
/>
|
||||||
borderRadius: '4px',
|
</div>
|
||||||
background: '#f59e0b',
|
<div className="form-group" style={{ marginBottom: '10px' }}>
|
||||||
color: '#121212',
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>签名</label>
|
||||||
fontSize: '16px',
|
<textarea
|
||||||
fontWeight: 'bold',
|
value={sm2Signature}
|
||||||
cursor: 'pointer',
|
onChange={(e) => setSm2Signature(e.target.value)}
|
||||||
transition: 'background 0.3s ease',
|
placeholder="输入签名 (hex)"
|
||||||
flex: 1
|
style={{
|
||||||
}}
|
width: '100%',
|
||||||
>
|
padding: '10px',
|
||||||
验证签名
|
border: '1px solid #333',
|
||||||
</button>
|
borderRadius: '4px',
|
||||||
</div>
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -895,7 +1033,7 @@ function App() {
|
|||||||
{/* 转换工具面板 */}
|
{/* 转换工具面板 */}
|
||||||
{activeTab === 'convert' && (
|
{activeTab === 'convert' && (
|
||||||
<div className="panel" style={{ backgroundColor: '#1e1e1e', padding: '20px', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.3)' }}>
|
<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' }}>Hex / UTF8 转换</h2>
|
<h2 style={{ color: '#f59e0b', marginBottom: '20px' }}>Hex / UTF8 / Base64 转换</h2>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '1rem', marginBottom: '1rem', flexDirection: isMobile ? 'column' : 'row' }}>
|
<div style={{ display: 'flex', gap: '1rem', marginBottom: '1rem', flexDirection: isMobile ? 'column' : 'row' }}>
|
||||||
<div className="form-group" style={{ flex: 1 }}>
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
@@ -953,8 +1091,64 @@ function App() {
|
|||||||
resize: 'vertical'
|
resize: 'vertical'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<div style={{ display: 'flex', gap: '10px', marginTop: '0.5rem' }}>
|
||||||
|
<button
|
||||||
|
onClick={handleHexToUtf8}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
background: '#f59e0b',
|
||||||
|
color: '#121212',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background 0.3s ease',
|
||||||
|
flex: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
UTF8 ← Hex
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleHexToBase64}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
background: '#f59e0b',
|
||||||
|
color: '#121212',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background 0.3s ease',
|
||||||
|
flex: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Base64 ← Hex
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group" style={{ flex: 1 }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>Base64 输入</label>
|
||||||
|
<textarea
|
||||||
|
value={base64Input}
|
||||||
|
onChange={(e) => setBase64Input(e.target.value)}
|
||||||
|
placeholder="输入 Base64 字符串"
|
||||||
|
style={{
|
||||||
|
height: '200px',
|
||||||
|
width: '100%',
|
||||||
|
padding: '10px',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: '#2d2d2d',
|
||||||
|
color: '#e0e0e0',
|
||||||
|
fontSize: '14px',
|
||||||
|
resize: 'vertical'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleHexToUtf8}
|
onClick={handleBase64ToHex}
|
||||||
style={{
|
style={{
|
||||||
marginTop: '0.5rem',
|
marginTop: '0.5rem',
|
||||||
padding: '8px 16px',
|
padding: '8px 16px',
|
||||||
@@ -968,13 +1162,97 @@ function App() {
|
|||||||
transition: 'background 0.3s ease'
|
transition: 'background 0.3s ease'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
UTF8 ← Hex
|
Hex ← Base64
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 证书工具面板 */}
|
||||||
|
{activeTab === 'cert' && (
|
||||||
|
<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 className="form-group" style={{ marginBottom: '20px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '10px', color: '#e0e0e0' }}>上传证书文件</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".cer,.crt,.pem"
|
||||||
|
onChange={handleCertFileUpload}
|
||||||
|
style={{
|
||||||
|
padding: '10px',
|
||||||
|
border: '1px solid #333',
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: '#2d2d2d',
|
||||||
|
color: '#e0e0e0',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{certInfo && (
|
||||||
|
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px', marginTop: '20px' }}>
|
||||||
|
<h3 style={{ color: '#f59e0b', marginBottom: '15px' }}>证书信息</h3>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>版本:</label>
|
||||||
|
<div style={{ color: '#e0e0e0' }}>{certInfo.version}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>序列号:</label>
|
||||||
|
<div style={{ color: '#e0e0e0', wordBreak: 'break-all' }}>{certInfo.serialNumber}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>所有者:</label>
|
||||||
|
<div style={{ color: '#e0e0e0' }}>
|
||||||
|
{Object.entries(certInfo.subject).map(([key, value]) => (
|
||||||
|
<div key={key}>{key}: {String(value)}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>签发者:</label>
|
||||||
|
<div style={{ color: '#e0e0e0' }}>
|
||||||
|
{Object.entries(certInfo.issuer).map(([key, value]) => (
|
||||||
|
<div key={key}>{key}: {String(value)}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>有效期:</label>
|
||||||
|
<div style={{ color: '#e0e0e0' }}>
|
||||||
|
<div>开始: {certInfo.notBefore}</div>
|
||||||
|
<div>结束: {certInfo.notAfter}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>公钥:</label>
|
||||||
|
<div style={{ color: '#e0e0e0', wordBreak: 'break-all' }}>{certInfo.publicKey}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0', fontWeight: 'bold' }}>用途:</label>
|
||||||
|
<div style={{ color: '#e0e0e0' }}>
|
||||||
|
{certInfo.keyUsage.length > 0 ? (
|
||||||
|
certInfo.keyUsage.map((usage: string, index: number) => (
|
||||||
|
<div key={index}>{usage}</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div>无</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 页脚 */}
|
{/* 页脚 */}
|
||||||
<footer style={{ marginTop: '2rem', padding: '1rem', borderTop: '1px solid #333', textAlign: 'center', color: '#e0e0e0' }}>
|
<footer style={{ marginTop: '2rem', padding: '1rem', borderTop: '1px solid #333', textAlign: 'center', color: '#e0e0e0' }}>
|
||||||
<p>© 2026 安全算法计算工具</p>
|
<p>© 2026 安全算法计算工具</p>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Vendored
+1
@@ -10,6 +10,7 @@ declare module 'sm-crypto' {
|
|||||||
doDecrypt: (ciphertext: string, privateKey: string) => string;
|
doDecrypt: (ciphertext: string, privateKey: string) => string;
|
||||||
doSignature: (message: string | Uint8Array, privateKey: string, options?: { hash?: boolean, der?: boolean, userId?: string, publicKey?: string, pointPool?: any[] }) => string;
|
doSignature: (message: string | Uint8Array, privateKey: string, options?: { hash?: boolean, der?: boolean, userId?: string, publicKey?: string, pointPool?: any[] }) => string;
|
||||||
doVerifySignature: (message: string | Uint8Array, signature: string, publicKey: string, options?: { hash?: boolean, der?: boolean, userId?: string }) => boolean;
|
doVerifySignature: (message: string | Uint8Array, signature: string, publicKey: string, options?: { hash?: boolean, der?: boolean, userId?: string }) => boolean;
|
||||||
|
getPublicKeyFromPrivateKey: (privateKey: string) => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sm3: {
|
export const sm3: {
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import forge from 'node-forge'
|
||||||
|
|
||||||
|
// 解析SM2证书信息
|
||||||
|
export const parseSm2Cert = (certData: string): any => {
|
||||||
|
try {
|
||||||
|
// 移除证书中的换行符和空格
|
||||||
|
const cleanCert = certData.replace(/\s/g, '')
|
||||||
|
|
||||||
|
// 解析DER编码的证书
|
||||||
|
const derBuffer = Buffer.from(cleanCert, 'base64')
|
||||||
|
const asn1 = forge.asn1.fromDer(derBuffer.toString('binary'))
|
||||||
|
const cert = forge.pki.certificateFromAsn1(asn1)
|
||||||
|
|
||||||
|
// 提取证书信息
|
||||||
|
const subject: any = {}
|
||||||
|
cert.subject.attributes.forEach((attr: any) => {
|
||||||
|
if (attr.name) {
|
||||||
|
subject[attr.name] = attr.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const issuer: any = {}
|
||||||
|
cert.issuer.attributes.forEach((attr: any) => {
|
||||||
|
if (attr.name) {
|
||||||
|
issuer[attr.name] = attr.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 提取公钥信息
|
||||||
|
const publicKey = cert.publicKey
|
||||||
|
let publicKeyHex = ''
|
||||||
|
|
||||||
|
// 尝试获取公钥的十六进制表示
|
||||||
|
try {
|
||||||
|
if (typeof publicKey === 'object' && publicKey !== null) {
|
||||||
|
// 尝试将公钥转换为PEM格式,然后提取十六进制
|
||||||
|
const pem = forge.pki.publicKeyToPem(publicKey)
|
||||||
|
// 从PEM中提取十六进制
|
||||||
|
publicKeyHex = pem.replace(/-----BEGIN PUBLIC KEY-----|-----END PUBLIC KEY-----|\s/g, '')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 如果转换失败,使用空字符串
|
||||||
|
publicKeyHex = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取证书用途
|
||||||
|
let keyUsage: string[] = []
|
||||||
|
if (cert.extensions) {
|
||||||
|
const keyUsageExt = cert.extensions.find((ext: any) => ext.id === 'keyUsage')
|
||||||
|
if (keyUsageExt && keyUsageExt.value) {
|
||||||
|
keyUsage = keyUsageExt.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: cert.version,
|
||||||
|
serialNumber: cert.serialNumber.toString(),
|
||||||
|
subject: subject,
|
||||||
|
issuer: issuer,
|
||||||
|
notBefore: cert.validity.notBefore,
|
||||||
|
notAfter: cert.validity.notAfter,
|
||||||
|
publicKey: publicKeyHex,
|
||||||
|
keyUsage: keyUsage
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Failed to parse certificate: ' + (error instanceof Error ? error.message : error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从文件内容解析证书
|
||||||
|
export const parseCertFromFile = (fileContent: ArrayBuffer): string => {
|
||||||
|
const buffer = Buffer.from(fileContent)
|
||||||
|
return buffer.toString('base64')
|
||||||
|
}
|
||||||
@@ -15,12 +15,15 @@ export const sm2GenerateKeyPair = (): { privateKey: string; publicKey: string }
|
|||||||
// SM2 从私钥计算公钥
|
// SM2 从私钥计算公钥
|
||||||
export const sm2GetPublicKeyFromPrivateKey = (privateKey: string): string => {
|
export const sm2GetPublicKeyFromPrivateKey = (privateKey: string): string => {
|
||||||
// 验证私钥是否为有效的 hex 字符串
|
// 验证私钥是否为有效的 hex 字符串
|
||||||
|
if (!privateKey) {
|
||||||
|
throw new Error('Private key is required')
|
||||||
|
}
|
||||||
if (!isValidHex(privateKey)) {
|
if (!isValidHex(privateKey)) {
|
||||||
throw new Error('Invalid private key: must be hex string')
|
throw new Error('Invalid private key: must be hex string')
|
||||||
}
|
}
|
||||||
|
|
||||||
const keypair = sm2.generateKeyPairHex(privateKey)
|
const publicKey = sm2.getPublicKeyFromPrivateKey(privateKey)
|
||||||
return keypair.publicKey
|
return publicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// SM2 加密
|
// SM2 加密
|
||||||
|
|||||||
@@ -9,3 +9,15 @@ export const utf8ToHex = (utf8: string): string => {
|
|||||||
const bytes = new TextEncoder().encode(utf8)
|
const bytes = new TextEncoder().encode(utf8)
|
||||||
return Array.from(bytes).map(byte => byte.toString(16).padStart(2, '0')).join('')
|
return Array.from(bytes).map(byte => byte.toString(16).padStart(2, '0')).join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hex 转 Base64
|
||||||
|
export const hexToBase64 = (hex: string): string => {
|
||||||
|
const bytes = new Uint8Array(hex.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || [])
|
||||||
|
return btoa(String.fromCharCode(...bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 转 Hex
|
||||||
|
export const base64ToHex = (base64: string): string => {
|
||||||
|
const bytes = new Uint8Array(Array.from(atob(base64), char => char.charCodeAt(0)))
|
||||||
|
return Array.from(bytes).map(byte => byte.toString(16).padStart(2, '0')).join('')
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
export const VERSION = "V1.0-20260418053331";
|
export const VERSION = "V1.0-20260418071243";
|
||||||
Reference in New Issue
Block a user