hotfox
This commit is contained in:
parent
32626c84a6
commit
4b3d5b762f
123
src/App.tsx
123
src/App.tsx
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -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 签名
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export const VERSION = "V1.0-20260424025112";
|
export const VERSION = "V1.0-20260428031400";
|
||||||
Loading…
Reference in New Issue
Block a user