整理
All checks were successful
Build and Deploy / build (push) Successful in 1m4s

This commit is contained in:
cheney 2026-04-18 15:53:54 +08:00
parent 88acc24e86
commit f4228370d9
3 changed files with 227 additions and 130 deletions

View File

@ -8,9 +8,22 @@ import { parseSm2Cert, parseCertFromFile } from './utils/algorithm/cert'
import { hexToUtf8, utf8ToHex, hexToBase64, base64ToHex } from './utils/convert'
function App() {
const [activeTab, setActiveTab] = useState('sm3')
// 从localStorage读取当前分类默认sm3
const getInitialTab = () => {
try {
const savedTab = localStorage.getItem('activeTab')
if (['sm2', 'sm3', 'cert'].includes(savedTab || '')) {
return savedTab || 'sm3'
}
} catch (error) {
console.error('Failed to read from localStorage:', error)
}
return 'sm3'
}
const [activeTab, setActiveTab] = useState(getInitialTab())
const [isMobile, setIsMobile] = useState(false)
const [mobileTab, setMobileTab] = useState('sm3')
const [mobileTab, setMobileTab] = useState(getInitialTab())
// 检测是否为移动设备
useEffect(() => {
@ -28,13 +41,27 @@ function App() {
// 处理移动设备的 tab 切换
const handleMobileTabChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setMobileTab(e.target.value)
setActiveTab(e.target.value)
const tab = e.target.value
setMobileTab(tab)
setActiveTab(tab)
// 保存到localStorage
try {
localStorage.setItem('activeTab', tab)
} catch (error) {
console.error('Failed to save to localStorage:', error)
}
}
// 处理桌面端的 tab 切换
const handleTabChange = (tab: string) => {
setActiveTab(tab)
setMobileTab(tab)
// 保存到localStorage
try {
localStorage.setItem('activeTab', tab)
} catch (error) {
console.error('Failed to save to localStorage:', error)
}
}
@ -162,12 +189,19 @@ function App() {
const reader = new FileReader()
reader.onload = (event) => {
const result = event.target?.result as ArrayBuffer
const result = event.target?.result
if (result) {
const base64Cert = parseCertFromFile(result)
handleParseCert(base64Cert)
try {
const base64Cert = parseCertFromFile(result as ArrayBuffer)
handleParseCert(base64Cert)
} catch (error) {
alert('证书读取失败: ' + (error instanceof Error ? error.message : String(error)))
}
}
}
reader.onerror = () => {
alert('证书文件读取失败')
}
reader.readAsArrayBuffer(file)
}
@ -1033,68 +1067,34 @@ function App() {
{/* 转换工具面板 */}
{activeTab === 'convert' && (
<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 / Base64 </h2>
<div style={{ display: 'flex', gap: '1rem', marginBottom: '1rem', flexDirection: isMobile ? 'column' : 'row' }}>
<div className="form-group" style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>UTF8 </label>
<textarea
value={utf8Input}
onChange={(e) => setUtf8Input(e.target.value)}
placeholder="输入 UTF8 字符串"
style={{
height: '200px',
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#2d2d2d',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical'
}}
/>
<button
onClick={handleUtf8ToHex}
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'
}}
>
UTF8 Hex
</button>
</div>
<div className="form-group" style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>Hex </label>
<textarea
value={hexInput}
onChange={(e) => setHexInput(e.target.value)}
placeholder="输入 hex 字符串"
style={{
height: '200px',
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#2d2d2d',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical'
}}
/>
<div style={{ display: 'flex', gap: '10px', marginTop: '0.5rem' }}>
<button
onClick={handleHexToUtf8}
{/* Hex / UTF8 互转卡片 */}
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px', marginBottom: '20px' }}>
<h3 style={{ color: '#e0e0e0', marginBottom: '15px' }}>Hex / UTF8 </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' }}>UTF8 </label>
<textarea
value={utf8Input}
onChange={(e) => setUtf8Input(e.target.value)}
placeholder="输入 UTF8 字符串"
style={{
height: '150px',
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#1e1e1e',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical'
}}
/>
<button
onClick={handleUtf8ToHex}
style={{
marginTop: '0.5rem',
padding: '8px 16px',
border: 'none',
borderRadius: '4px',
@ -1103,15 +1103,78 @@ function App() {
fontSize: '14px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease',
flex: 1
transition: 'background 0.3s ease'
}}
>
UTF8 Hex
</button>
</div>
<div className="form-group" style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>Hex </label>
<textarea
value={hexInput}
onChange={(e) => setHexInput(e.target.value)}
placeholder="输入 hex 字符串"
style={{
height: '150px',
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#1e1e1e',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical'
}}
/>
<button
onClick={handleHexToUtf8}
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'
}}
>
UTF8 Hex
</button>
<button
onClick={handleHexToBase64}
</div>
</div>
</div>
{/* Hex / Base64 互转卡片 */}
<div style={{ backgroundColor: '#2d2d2d', padding: '15px', borderRadius: '8px' }}>
<h3 style={{ color: '#e0e0e0', marginBottom: '15px' }}>Hex / Base64 </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' }}>Base64 </label>
<textarea
value={base64Input}
onChange={(e) => setBase64Input(e.target.value)}
placeholder="输入 Base64 字符串"
style={{
height: '150px',
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#1e1e1e',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical'
}}
/>
<button
onClick={handleBase64ToHex}
style={{
marginTop: '0.5rem',
padding: '8px 16px',
border: 'none',
borderRadius: '4px',
@ -1120,51 +1183,50 @@ function App() {
fontSize: '14px',
fontWeight: 'bold',
cursor: 'pointer',
transition: 'background 0.3s ease',
flex: 1
transition: 'background 0.3s ease'
}}
>
Hex Base64
</button>
</div>
<div className="form-group" style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: '5px', color: '#e0e0e0' }}>Hex </label>
<textarea
value={hexInput}
onChange={(e) => setHexInput(e.target.value)}
placeholder="输入 hex 字符串"
style={{
height: '150px',
width: '100%',
padding: '10px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#1e1e1e',
color: '#e0e0e0',
fontSize: '14px',
resize: 'vertical'
}}
/>
<button
onClick={handleHexToBase64}
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'
}}
>
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
onClick={handleBase64ToHex}
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'
}}
>
Hex Base64
</button>
</div>
</div>
</div>
)}
@ -1172,23 +1234,41 @@ function App() {
{/* 证书工具面板 */}
{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>
<h2 style={{ color: '#f59e0b', marginBottom: '20px' }}>RSA </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',
<div style={{ position: 'relative', display: 'inline-block', width: '100%' }}>
<input
type="file"
accept=".cer,.crt,.pem"
onChange={handleCertFileUpload}
style={{
position: 'absolute',
opacity: 0,
width: '100%',
height: '100%',
cursor: 'pointer'
}}
/>
<div style={{
padding: '12px 20px',
border: '1px solid #333',
borderRadius: '4px',
backgroundColor: '#2d2d2d',
color: '#e0e0e0',
cursor: 'pointer'
}}
/>
color: '#f59e0b',
fontSize: '16px',
fontWeight: 'bold',
textAlign: 'center',
cursor: 'pointer',
transition: 'background 0.3s ease'
}}>
</div>
</div>
<div style={{ marginTop: '10px', color: '#888', fontSize: '14px' }}>
.cer, .crt, .pem
</div>
</div>
{certInfo && (

View File

@ -1,14 +1,28 @@
import forge from 'node-forge'
// 将ArrayBuffer转换为Base64字符串浏览器兼容
const arrayBufferToBase64 = (buffer: ArrayBuffer): string => {
const bytes = new Uint8Array(buffer)
let binary = ''
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i])
}
return btoa(binary)
}
// 解析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'))
// 解析DER编码的证书浏览器兼容
const binary = atob(cleanCert)
const derBuffer = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
derBuffer[i] = binary.charCodeAt(i)
}
const asn1 = forge.asn1.fromDer(String.fromCharCode(...derBuffer))
const cert = forge.pki.certificateFromAsn1(asn1)
// 提取证书信息
@ -27,16 +41,20 @@ export const parseSm2Cert = (certData: string): any => {
})
// 提取公钥信息
const publicKey = cert.publicKey
let publicKeyHex = ''
// 尝试获取公钥的十六进制表示
try {
if (typeof publicKey === 'object' && publicKey !== null) {
if (cert.publicKey) {
// 尝试将公钥转换为PEM格式然后提取十六进制
const pem = forge.pki.publicKeyToPem(publicKey)
// 从PEM中提取十六进制
publicKeyHex = pem.replace(/-----BEGIN PUBLIC KEY-----|-----END PUBLIC KEY-----|\s/g, '')
try {
const pem = forge.pki.publicKeyToPem(cert.publicKey)
// 从PEM中提取十六进制
publicKeyHex = pem.replace(/-----BEGIN PUBLIC KEY-----|-----END PUBLIC KEY-----|\s/g, '')
} catch (e) {
// 如果无法转换为PEM使用空字符串
publicKeyHex = ''
}
}
} catch (e) {
// 如果转换失败,使用空字符串
@ -69,6 +87,5 @@ export const parseSm2Cert = (certData: string): any => {
// 从文件内容解析证书
export const parseCertFromFile = (fileContent: ArrayBuffer): string => {
const buffer = Buffer.from(fileContent)
return buffer.toString('base64')
return arrayBufferToBase64(fileContent)
}

View File

@ -1 +1 @@
export const VERSION = "V1.0-20260418071243";
export const VERSION = "V1.0-20260418075022";