This commit is contained in:
cheney 2026-04-28 11:18:22 +08:00
parent 32626c84a6
commit 4b3d5b762f
3 changed files with 132 additions and 11 deletions

View File

@ -3,7 +3,7 @@ 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, sm2DerToC1C3C2, sm2C1C3C2ToDer } from './utils/algorithm/sm2'
import { parseSm2Cert, parseCertFromFile, generateRootCert, generateAppCert } from './utils/algorithm/cert' import { parseSm2Cert, parseCertFromFile, generateRootCert, generateAppCert } from './utils/algorithm/cert'
import { hexToUtf8, utf8ToHex, hexToBase64, base64ToHex } from './utils/convert' import { hexToUtf8, utf8ToHex, hexToBase64, base64ToHex } from './utils/convert'
@ -172,6 +172,8 @@ function App() {
const [hexInput, setHexInput] = useState('') const [hexInput, setHexInput] = useState('')
const [utf8Input, setUtf8Input] = useState('') const [utf8Input, setUtf8Input] = useState('')
const [base64Input, setBase64Input] = useState('') const [base64Input, setBase64Input] = useState('')
const [derInput, setDerInput] = useState('')
const [c1c3c2Input, setC1c3c2Input] = useState('')
// 证书工具状态 // 证书工具状态
const [certInfo, setCertInfo] = useState<any>(null) const [certInfo, setCertInfo] = useState<any>(null)
@ -262,6 +264,26 @@ function App() {
} }
} }
// DER 转 C1C3C2
const handleDerToC1C3C2 = () => {
try {
const c1c3c2 = sm2DerToC1C3C2(derInput)
setC1c3c2Input(c1c3c2)
} catch (error) {
alert('转换失败: ' + (error instanceof Error ? error.message : error))
}
}
// C1C3C2 转 DER
const handleC1C3C2ToDer = () => {
try {
const der = sm2C1C3C2ToDer(c1c3c2Input)
setDerInput(der)
} catch (error) {
alert('转换失败: ' + (error instanceof Error ? error.message : error))
}
}
// 处理证书文件上传 // 处理证书文件上传
const handleCertFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => { const handleCertFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0] const file = e.target.files?.[0]
@ -353,7 +375,10 @@ function App() {
const handleDecryptSm2 = () => { const handleDecryptSm2 = () => {
try { try {
const decrypted = sm2Decrypt(sm2Encrypted, sm2PrivateKey) const decrypted = sm2Decrypt(sm2Encrypted, sm2PrivateKey)
setSm2Decrypted(decrypted) const hexResult = Array.from(decrypted)
.map(byte => byte.toString(16).padStart(2, '0'))
.join('')
setSm2Decrypted(hexResult)
} catch (error) { } catch (error) {
alert('解密失败: ' + (error instanceof Error ? error.message : error)) alert('解密失败: ' + (error instanceof Error ? error.message : error))
} }
@ -1470,6 +1495,100 @@ function App() {
</div> </div>
</div> </div>
</div> </div>
{/* DER / C1C3C2 互转卡片 */}
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px', marginTop: '20px' }}>
<h3 style={{ color: '#e0e0e0', marginBottom: '15px' }}>SM2 DER / C1C3C2 </h3>
<div style={{ display: 'flex', gap: '1rem', flexDirection: isMobile ? 'column' : 'row' }}>
<div className="form-group" style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>DER (hex)</label>
<textarea
value={derInput}
onChange={(e) => setDerInput(e.target.value)}
onContextMenu={(e) => openMenu(e, derInput, setDerInput, false)}
onTouchStart={(e) => {
const touchTimer = setTimeout(() => {
openMenu(e, derInput, setDerInput, false)
}, 500)
return () => clearTimeout(touchTimer)
}}
placeholder="输入 DER 格式密文 (hex)"
style={{
height: '150px',
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#1e1e1e',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical'
}}
/>
<button
onClick={handleDerToC1C3C2}
style={{
marginTop: '0.5rem',
padding: '8px 16px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '14px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease'
}}
>
DER C1C3C2
</button>
</div>
<div className="form-group" style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>C1C3C2 (hex)</label>
<textarea
value={c1c3c2Input}
onChange={(e) => setC1c3c2Input(e.target.value)}
onContextMenu={(e) => openMenu(e, c1c3c2Input, setC1c3c2Input, false)}
onTouchStart={(e) => {
const touchTimer = setTimeout(() => {
openMenu(e, c1c3c2Input, setC1c3c2Input, false)
}, 500)
return () => clearTimeout(touchTimer)
}}
placeholder="输入 C1C3C2 格式密文 (hex)"
style={{
height: '150px',
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#1e1e1e',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical'
}}
/>
<button
onClick={handleC1C3C2ToDer}
style={{
marginTop: '0.5rem',
padding: '8px 16px',
border: 'none',
borderRadius: '4px',
background: '#f59e0b',
color: '#121212',
fontSize: '14px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease'
}}
>
C1C3C2 DER
</button>
</div>
</div>
</div>
</div> </div>
)} )}

View File

@ -51,7 +51,7 @@ export const sm2Encrypt = (message: string, publicKey: string, cipherMode: numbe
} }
// SM2 解密 // SM2 解密
export const sm2Decrypt = (ciphertext: string, privateKey: string, cipherMode: number = 1): string => { export const sm2Decrypt = (ciphertext: string, privateKey: string, cipherMode: number = 1): Uint8Array => {
// 验证密文是否为有效的 hex 字符串 // 验证密文是否为有效的 hex 字符串
if (!isValidHex(ciphertext)) { if (!isValidHex(ciphertext)) {
throw new Error('Invalid ciphertext: must be hex string') throw new Error('Invalid ciphertext: must be hex string')
@ -61,13 +61,15 @@ export const sm2Decrypt = (ciphertext: string, privateKey: string, cipherMode: n
throw new Error('Invalid private key: must be hex string') throw new Error('Invalid private key: must be hex string')
} }
// 解密 // 解密,指定 output 为 'bytes' 以获取 Uint8Array 格式
const decrypted = sm2Extended.doDecrypt(ciphertext, privateKey, cipherMode) const decrypted = sm2Extended.doDecrypt(ciphertext, privateKey, cipherMode, { output: 'bytes' })
// 将解密结果转换为 hex 字符串
const decryptedBytes = new TextEncoder().encode(decrypted) // 如果返回的是字符串,则转为 Uint8Array
return Array.from(decryptedBytes) if (typeof decrypted === 'string') {
.map(byte => byte.toString(16).padStart(2, '0')) return new TextEncoder().encode(decrypted)
.join('') }
return decrypted
} }
// SM2 签名 // SM2 签名

View File

@ -1 +1 @@
export const VERSION = "V1.0-20260424025112"; export const VERSION = "V1.0-20260428031400";