添加导入加密插件"laoqianjunzi-crypto"
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
## 1.0.0(2026-05-30)
|
||||||
|
初次提交
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"id": "laoqianjunzi-crypto",
|
||||||
|
"displayName": "多端加密工具箱",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "为 uni-app x 提供 Android、iOS、Harmony、Web、微信小程序统一加密能力,覆盖摘要、编码、HMAC、AES、DES、RC4 与 RSA。",
|
||||||
|
"keywords": [
|
||||||
|
"crypto",
|
||||||
|
"hash",
|
||||||
|
"aes",
|
||||||
|
"rsa",
|
||||||
|
"uts"
|
||||||
|
],
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^5.07",
|
||||||
|
"uni-app": "",
|
||||||
|
"uni-app-x": "^4.75"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "uts",
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "所有加解密与编码运算均在本地执行,不内置远程上传逻辑",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"darkmode": "√",
|
||||||
|
"i18n": "√",
|
||||||
|
"widescreen": "√",
|
||||||
|
"npmurl": "https://gitee.com/laoqianjunzi/laoqianjunzi-crypto"
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"client": {
|
||||||
|
"uni-app": {
|
||||||
|
"vue": {
|
||||||
|
"vue2": "-",
|
||||||
|
"vue3": "-"
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"safari": "-",
|
||||||
|
"chrome": "-"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"vue": "-",
|
||||||
|
"nvue": "-",
|
||||||
|
"android": "-",
|
||||||
|
"ios": "-",
|
||||||
|
"harmony": "-"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "-",
|
||||||
|
"alipay": "-",
|
||||||
|
"toutiao": "-",
|
||||||
|
"baidu": "-",
|
||||||
|
"kuaishou": "-",
|
||||||
|
"jd": "-",
|
||||||
|
"harmony": "-",
|
||||||
|
"qq": "-",
|
||||||
|
"lark": "-"
|
||||||
|
},
|
||||||
|
"quickapp": {
|
||||||
|
"huawei": "-",
|
||||||
|
"union": "-"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uni-app-x": {
|
||||||
|
"web": {
|
||||||
|
"safari": "√",
|
||||||
|
"chrome": "√"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"android": {
|
||||||
|
"extVersion": "",
|
||||||
|
"minVersion": "21"
|
||||||
|
},
|
||||||
|
"ios": "√",
|
||||||
|
"harmony": "√"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "√"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "√",
|
||||||
|
"aliyun": "√",
|
||||||
|
"alipay": "√"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,518 @@
|
|||||||
|
<template>
|
||||||
|
<!-- #ifdef APP -->
|
||||||
|
<scroll-view class="crypto-scroll">
|
||||||
|
<!-- #endif -->
|
||||||
|
<view class="crypto-page">
|
||||||
|
<view class="hero-panel">
|
||||||
|
<text class="hero-kicker">LAOQIANJUNZI-CRYPTO</text>
|
||||||
|
<text class="hero-title">统一加密能力演示</text>
|
||||||
|
<text class="hero-desc">这一页直接调用插件导出的新接口,集中演示编码、摘要、HMAC、AES、3DES、RC4、RSA 与字节数组加解密。</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card-block runtime-card">
|
||||||
|
<text class="card-title">运行时概览</text>
|
||||||
|
<view class="status-row">
|
||||||
|
<text class="status-label">平台</text>
|
||||||
|
<text class="status-value">{{ runtimeInfo.platformName }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-row">
|
||||||
|
<text class="status-label">引擎</text>
|
||||||
|
<text class="status-value">{{ runtimeInfo.engineName }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-row">
|
||||||
|
<text class="status-label">高级模式</text>
|
||||||
|
<text class="status-value">{{ runtimeInfo.supportsAdvancedModes ? '已启用' : '受限' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-row">
|
||||||
|
<text class="status-label">原生桥接</text>
|
||||||
|
<text class="status-value">{{ runtimeInfo.usesNativeBridge ? '是' : '否' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card-block form-card">
|
||||||
|
<text class="card-title">输入参数</text>
|
||||||
|
<view class="field-group">
|
||||||
|
<text class="field-label">明文</text>
|
||||||
|
<input class="field-input" v-model="plainText" />
|
||||||
|
</view>
|
||||||
|
<view class="field-group">
|
||||||
|
<text class="field-label">主密钥</text>
|
||||||
|
<input class="field-input" v-model="secretText" />
|
||||||
|
</view>
|
||||||
|
<view class="field-group">
|
||||||
|
<text class="field-label">向量</text>
|
||||||
|
<input class="field-input" v-model="ivText" />
|
||||||
|
</view>
|
||||||
|
<view class="field-group">
|
||||||
|
<text class="field-label">HMAC 密钥</text>
|
||||||
|
<input class="field-input" v-model="macSecretText" />
|
||||||
|
</view>
|
||||||
|
<view class="field-group">
|
||||||
|
<text class="field-label">3DES 密钥</text>
|
||||||
|
<input class="field-input" v-model="legacySecretText" />
|
||||||
|
</view>
|
||||||
|
<view class="field-group">
|
||||||
|
<text class="field-label">RSA 位数</text>
|
||||||
|
<input class="field-input" type="number" v-model="rsaBitsText" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="action-grid">
|
||||||
|
<button class="action-button action-strong" @click="runAllDemos">运行全部示例</button>
|
||||||
|
<button class="action-button" @click="runCodecDemo">编码</button>
|
||||||
|
<button class="action-button" @click="runDigestDemo">摘要/HMAC</button>
|
||||||
|
<button class="action-button" @click="runAesDemo">AES 文本</button>
|
||||||
|
<button class="action-button" @click="runByteDemo">AES 字节</button>
|
||||||
|
<button class="action-button" @click="runLegacyDemo">3DES/RC4</button>
|
||||||
|
<button class="action-button" @click="runRsaDemo">RSA</button>
|
||||||
|
<button class="action-button" @click="runCompatDemo">兼容 API</button>
|
||||||
|
<button class="action-button action-warn" @click="clearResults">清空结果</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="result-grid">
|
||||||
|
<view class="card-block result-card">
|
||||||
|
<text class="card-title">编码</text>
|
||||||
|
<text class="result-line">Base64: {{ codecBase64 }}</text>
|
||||||
|
<text class="result-line">Hex: {{ codecHex }}</text>
|
||||||
|
<text class="result-line">Base64url: {{ codecBase64url }}</text>
|
||||||
|
<text class="result-line">反解: {{ codecBackText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card-block result-card">
|
||||||
|
<text class="card-title">摘要与签名</text>
|
||||||
|
<text class="result-line">MD5: {{ md5Text }}</text>
|
||||||
|
<text class="result-line">SHA256: {{ sha256Text }}</text>
|
||||||
|
<text class="result-line">SHA512: {{ sha512Text }}</text>
|
||||||
|
<text class="result-line">HMAC-SHA256: {{ hmacText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card-block result-card">
|
||||||
|
<text class="card-title">AES 文本</text>
|
||||||
|
<text class="result-line">密文: {{ aesCipherText }}</text>
|
||||||
|
<text class="result-line">解密: {{ aesPlainText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card-block result-card">
|
||||||
|
<text class="card-title">AES 字节</text>
|
||||||
|
<text class="result-line">字节密文: {{ aesByteCipherHex }}</text>
|
||||||
|
<text class="result-line">字节解密: {{ aesBytePlainText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card-block result-card">
|
||||||
|
<text class="card-title">3DES 与 RC4</text>
|
||||||
|
<text class="result-line">3DES 密文: {{ tripleCipherText }}</text>
|
||||||
|
<text class="result-line">3DES 解密: {{ triplePlainText }}</text>
|
||||||
|
<text class="result-line">RC4 密文: {{ rc4CipherHex }}</text>
|
||||||
|
<text class="result-line">RC4 解密: {{ rc4PlainText }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card-block result-card">
|
||||||
|
<text class="card-title">RSA</text>
|
||||||
|
<text class="result-line">公钥长度: {{ rsaPublicBrief }}</text>
|
||||||
|
<text class="result-line">私钥长度: {{ rsaPrivateBrief }}</text>
|
||||||
|
<text class="result-line">RSA 密文: {{ rsaCipherText }}</text>
|
||||||
|
<text class="result-line">RSA 解密: {{ rsaPlainText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="card-block log-card">
|
||||||
|
<text class="card-title">执行日志</text>
|
||||||
|
<view class="log-list">
|
||||||
|
<text class="log-line" v-for="(item,index) in logLines" :key="index">{{ item }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- #ifdef APP -->
|
||||||
|
</scroll-view>
|
||||||
|
<!-- #endif -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="uts">
|
||||||
|
import { aesDecrypt, aesDecryptBytes, aesDecryptText, aesEncrypt, aesEncryptBytes, aesEncryptText, base64Decode, base64Encode, createRsaPair, defaultByteCipherOptions, defaultTextCipherOptions, defaultTripleDesOptions, decodeText, describeCryptoRuntime, digestText, encodeText, generateRSAKeyPair, md5, rc4DecryptText, rc4EncryptText, rsaDecrypt, rsaDecryptText, rsaEncrypt, rsaEncryptText, signText, tripleDesDecryptText, tripleDesEncryptText, useCrypto } from '@/uni_modules/laoqianjunzi-crypto'
|
||||||
|
import type { CryptoRuntimeSnapshot } from '@/uni_modules/laoqianjunzi-crypto'
|
||||||
|
|
||||||
|
const runtimeInfo = ref(describeCryptoRuntime() as CryptoRuntimeSnapshot)
|
||||||
|
const plainText = ref('Hello laoqianjunzi-crypto')
|
||||||
|
const secretText = ref('1234567890abcdef')
|
||||||
|
const ivText = ref('fedcba0987654321')
|
||||||
|
const macSecretText = ref('signing-key')
|
||||||
|
const legacySecretText = ref('1234567890abcdef12345678')
|
||||||
|
const rsaBitsText = ref('2048')
|
||||||
|
|
||||||
|
const codecBase64 = ref('')
|
||||||
|
const codecHex = ref('')
|
||||||
|
const codecBase64url = ref('')
|
||||||
|
const codecBackText = ref('')
|
||||||
|
|
||||||
|
const md5Text = ref('')
|
||||||
|
const sha256Text = ref('')
|
||||||
|
const sha512Text = ref('')
|
||||||
|
const hmacText = ref('')
|
||||||
|
|
||||||
|
const aesCipherText = ref('')
|
||||||
|
const aesPlainText = ref('')
|
||||||
|
const aesByteCipherHex = ref('')
|
||||||
|
const aesBytePlainText = ref('')
|
||||||
|
|
||||||
|
const tripleCipherText = ref('')
|
||||||
|
const triplePlainText = ref('')
|
||||||
|
const rc4CipherHex = ref('')
|
||||||
|
const rc4PlainText = ref('')
|
||||||
|
|
||||||
|
const rsaCipherText = ref('')
|
||||||
|
const rsaPlainText = ref('')
|
||||||
|
const rsaPublicBrief = ref('')
|
||||||
|
const rsaPrivateBrief = ref('')
|
||||||
|
|
||||||
|
const logLines = ref(['等待执行示例'] as string[])
|
||||||
|
|
||||||
|
function appendLog(message : string) : void {
|
||||||
|
const stamp = new Date().toISOString()
|
||||||
|
logLines.value.unshift(stamp + ' ' + message)
|
||||||
|
if (logLines.value.length > 20) {
|
||||||
|
logLines.value.splice(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToHex(input : Uint8Array) : string {
|
||||||
|
let output = ''
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
let part = input[index].toString(16)
|
||||||
|
if (part.length < 2) {
|
||||||
|
part = '0' + part
|
||||||
|
}
|
||||||
|
output += part
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function textToBytes(input : string) : Uint8Array {
|
||||||
|
const output = new Uint8Array(input.length)
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
const code = input.charCodeAt(index)
|
||||||
|
output[index] = code == null ? 0 : code & 0xff
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToText(input : Uint8Array) : string {
|
||||||
|
let output = ''
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
output += String.fromCharCode(input[index])
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentRsaBits() : number {
|
||||||
|
const parsed = parseInt(rsaBitsText.value)
|
||||||
|
if (parsed == 1024 || parsed == 2048 || parsed == 3072 || parsed == 4096) {
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
return 2048
|
||||||
|
}
|
||||||
|
|
||||||
|
function runCodecDemo() : void {
|
||||||
|
codecBase64.value = encodeText('base64', plainText.value)
|
||||||
|
codecHex.value = encodeText('hex', plainText.value)
|
||||||
|
codecBase64url.value = encodeText('base64url', plainText.value)
|
||||||
|
codecBackText.value = decodeText('base64', codecBase64.value)
|
||||||
|
appendLog('编码能力已完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
function runDigestDemo() : void {
|
||||||
|
md5Text.value = digestText('md5', plainText.value)
|
||||||
|
sha256Text.value = digestText('sha256', plainText.value)
|
||||||
|
sha512Text.value = digestText('sha512', plainText.value)
|
||||||
|
hmacText.value = signText('sha256', macSecretText.value, plainText.value)
|
||||||
|
appendLog('摘要与 HMAC 已完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAesDemo() : void {
|
||||||
|
const options = defaultTextCipherOptions()
|
||||||
|
options.mode = 'CBC'
|
||||||
|
options.padding = 'PKCS7'
|
||||||
|
options.ivText = ivText.value
|
||||||
|
options.keyLength = 16
|
||||||
|
aesCipherText.value = aesEncryptText(secretText.value, plainText.value, options)
|
||||||
|
aesPlainText.value = aesDecryptText(secretText.value, aesCipherText.value, options)
|
||||||
|
appendLog('AES 文本加解密已完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
function runByteDemo() : void {
|
||||||
|
const options = defaultByteCipherOptions()
|
||||||
|
options.mode = 'CTR'
|
||||||
|
options.padding = 'PKCS7'
|
||||||
|
options.keyLength = 16
|
||||||
|
const secretBytes = textToBytes(secretText.value)
|
||||||
|
const plainBytes = textToBytes(plainText.value)
|
||||||
|
const cipherBytes = aesEncryptBytes(secretBytes, plainBytes, options)
|
||||||
|
const restoreBytes = aesDecryptBytes(secretBytes, cipherBytes, options)
|
||||||
|
aesByteCipherHex.value = bytesToHex(cipherBytes)
|
||||||
|
aesBytePlainText.value = bytesToText(restoreBytes)
|
||||||
|
appendLog('AES 字节加解密已完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
function runLegacyDemo() : void {
|
||||||
|
const options = defaultTripleDesOptions()
|
||||||
|
options.mode = 'CBC'
|
||||||
|
options.ivText = '12345678'
|
||||||
|
tripleCipherText.value = tripleDesEncryptText(legacySecretText.value, plainText.value, options)
|
||||||
|
triplePlainText.value = tripleDesDecryptText(legacySecretText.value, tripleCipherText.value, options)
|
||||||
|
rc4CipherHex.value = rc4EncryptText(macSecretText.value, plainText.value)
|
||||||
|
rc4PlainText.value = rc4DecryptText(macSecretText.value, rc4CipherHex.value)
|
||||||
|
appendLog('3DES 与 RC4 已完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
function runRsaDemo() : void {
|
||||||
|
const pair = createRsaPair(currentRsaBits())
|
||||||
|
rsaPublicBrief.value = pair.publicKey.length + ' chars'
|
||||||
|
rsaPrivateBrief.value = pair.privateKey.length + ' chars'
|
||||||
|
rsaCipherText.value = rsaEncryptText(pair.publicKey, plainText.value, 'base64')
|
||||||
|
rsaPlainText.value = rsaDecryptText(pair.privateKey, rsaCipherText.value, 'base64')
|
||||||
|
appendLog('RSA 生成与加解密已完成')
|
||||||
|
}
|
||||||
|
|
||||||
|
function runCompatDemo() : void {
|
||||||
|
const xBase64 = base64Encode(plainText.value)
|
||||||
|
const xPlain = base64Decode(xBase64)
|
||||||
|
const xMd5 = md5(plainText.value)
|
||||||
|
const xCipher = aesEncrypt(secretText.value, plainText.value, 'CBC', ivText.value, 16)
|
||||||
|
const xRestore = aesDecrypt(secretText.value, xCipher, 'CBC', ivText.value, 16)
|
||||||
|
const xPair = generateRSAKeyPair(currentRsaBits())
|
||||||
|
const xRsaText = rsaDecrypt(xPair.privateKey, rsaEncrypt(xPair.publicKey, plainText.value))
|
||||||
|
const crypto = useCrypto()
|
||||||
|
const limeIv = crypto.enc.Utf8.parse(ivText.value)
|
||||||
|
const limeCipher = crypto.AES.encrypt(plainText.value, secretText.value, {
|
||||||
|
iv: limeIv,
|
||||||
|
mode: crypto.mode.CBC,
|
||||||
|
padding: crypto.pad.Pkcs7
|
||||||
|
} as UTSJSONObject)
|
||||||
|
const limePlain = crypto.AES.decrypt(limeCipher.toString(), secretText.value, {
|
||||||
|
iv: limeIv,
|
||||||
|
mode: crypto.mode.CBC,
|
||||||
|
padding: crypto.pad.Pkcs7
|
||||||
|
} as UTSJSONObject).toString(crypto.enc.Utf8)
|
||||||
|
const limeRsa = crypto.RSA
|
||||||
|
const limePair = limeRsa.generateKeyPair()
|
||||||
|
limeRsa.setPublicKey(limePair.publicKey)
|
||||||
|
const limeCipherText = limeRsa.encrypt(plainText.value)
|
||||||
|
limeRsa.setPrivateKey(limePair.privateKey)
|
||||||
|
const limeRsaText = limeRsa.decrypt(limeCipherText)
|
||||||
|
appendLog('兼容 API 已完成:x-base64=' + xPlain.length + ' x-md5=' + xMd5.substring(0, 8) + ' x-aes=' + xRestore.length + ' x-rsa=' + xRsaText.length + ' lime-aes=' + limePlain.length + ' lime-rsa=' + limeRsaText.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAllDemos() : void {
|
||||||
|
try {
|
||||||
|
runCodecDemo()
|
||||||
|
runDigestDemo()
|
||||||
|
runAesDemo()
|
||||||
|
runByteDemo()
|
||||||
|
runLegacyDemo()
|
||||||
|
runRsaDemo()
|
||||||
|
runCompatDemo()
|
||||||
|
appendLog('全部能力演示执行结束')
|
||||||
|
} catch (error) {
|
||||||
|
appendLog('执行失败:' + (error as Error).message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearResults() : void {
|
||||||
|
codecBase64.value = ''
|
||||||
|
codecHex.value = ''
|
||||||
|
codecBase64url.value = ''
|
||||||
|
codecBackText.value = ''
|
||||||
|
md5Text.value = ''
|
||||||
|
sha256Text.value = ''
|
||||||
|
sha512Text.value = ''
|
||||||
|
hmacText.value = ''
|
||||||
|
aesCipherText.value = ''
|
||||||
|
aesPlainText.value = ''
|
||||||
|
aesByteCipherHex.value = ''
|
||||||
|
aesBytePlainText.value = ''
|
||||||
|
tripleCipherText.value = ''
|
||||||
|
triplePlainText.value = ''
|
||||||
|
rc4CipherHex.value = ''
|
||||||
|
rc4PlainText.value = ''
|
||||||
|
rsaCipherText.value = ''
|
||||||
|
rsaPlainText.value = ''
|
||||||
|
rsaPublicBrief.value = ''
|
||||||
|
rsaPrivateBrief.value = ''
|
||||||
|
logLines.value = ['结果已清空'] as string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
appendLog('示例页已就绪,点击按钮即可执行')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.crypto-scroll {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crypto-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 48rpx;
|
||||||
|
background-color: #efe8dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 40rpx;
|
||||||
|
border-radius: 48rpx;
|
||||||
|
background-color: #11231c;
|
||||||
|
margin-bottom: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-kicker {
|
||||||
|
color: #f2c169;
|
||||||
|
font-size: 26rpx;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
color: #fff8ea;
|
||||||
|
font-size: 56rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-desc {
|
||||||
|
color: #c9d6cc;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 36rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
background-color: #fffaf1;
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: #24352e;
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runtime-card {
|
||||||
|
background-color: #f7f1e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
border-bottom-width: 2rpx;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-color: #e7dbc8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
color: #6b6d66;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-value {
|
||||||
|
color: #1c2b25;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card {
|
||||||
|
background-color: #fff7ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-label {
|
||||||
|
color: #6f6258;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-input {
|
||||||
|
height: 84rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
background-color: #f1e7da;
|
||||||
|
color: #1d2823;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
min-width: 208rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background-color: #d8e5db;
|
||||||
|
color: #20312a;
|
||||||
|
font-size: 26rpx;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-strong {
|
||||||
|
background-color: #17392d;
|
||||||
|
color: #fff8ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-warn {
|
||||||
|
background-color: #ead4b9;
|
||||||
|
color: #5d3722;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
background-color: #fffdf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-line {
|
||||||
|
color: #25322c;
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 38rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-card {
|
||||||
|
background-color: #15261f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line {
|
||||||
|
color: #d6e7dc;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,483 @@
|
|||||||
|
# laoqianjunzi-crypto
|
||||||
|
|
||||||
|
`laoqianjunzi-crypto` 是一个面向 `uni-app x` 的多端加密工具插件,统一封装了文本编解码、摘要、HMAC、AES、DES、3DES、RC4、RSA 等常见能力。
|
||||||
|
|
||||||
|
插件当前覆盖以下平台:
|
||||||
|
|
||||||
|
- `App-Android`
|
||||||
|
- `App-iOS`
|
||||||
|
- `App-Harmony`
|
||||||
|
- `Web`
|
||||||
|
- `微信小程序`
|
||||||
|
|
||||||
|
适合以下场景:
|
||||||
|
|
||||||
|
- 业务请求签名、摘要校验、口令派生前的基础处理
|
||||||
|
- 本地敏感文本或字节数据加解密
|
||||||
|
- 公私钥生成、RSA 短文本加解密
|
||||||
|
- 需要在多端复用同一套加密接口的 `uni-app x` 项目
|
||||||
|
|
||||||
|
## 功能概览
|
||||||
|
|
||||||
|
- 支持 `utf8`、`hex`、`base64`、`base64url`、`latin1`、`utf16` 编解码
|
||||||
|
- 支持 `md5`、`sha1`、`sha256`、`sha512` 摘要
|
||||||
|
- 支持 `HMAC-SHA1`、`HMAC-SHA256`、`HMAC-SHA512`
|
||||||
|
- 支持 AES 文本加解密与 AES 字节数组加解密
|
||||||
|
- 支持 DES、3DES、RC4 文本加解密
|
||||||
|
- 支持 RSA 密钥对生成与短文本加解密
|
||||||
|
- 支持读取当前运行时的底层引擎信息
|
||||||
|
- 所有计算均在本地执行,不包含网络上传逻辑
|
||||||
|
|
||||||
|
## 版本要求
|
||||||
|
|
||||||
|
根据插件当前 `package.json` 配置,建议使用以下环境:
|
||||||
|
|
||||||
|
- `HBuilderX` `5.07` 或更高版本
|
||||||
|
- `uni-app x` `4.75` 或更高版本
|
||||||
|
|
||||||
|
## 平台支持矩阵
|
||||||
|
|
||||||
|
| 平台 | 加密能力 | RSA | 运行时引擎 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| App-Android | 支持 | 支持 | `JCA` 原生桥接 |
|
||||||
|
| App-iOS | 支持 | 支持 | `CommonCrypto` 原生桥接 |
|
||||||
|
| App-Harmony | 支持 | 支持 | `@ohos/crypto-js` + 原生 RSA |
|
||||||
|
| Web | 支持 | 支持 | `crypto-es` + `jsencrypt` |
|
||||||
|
| 微信小程序 | 支持 | 支持 | `crypto-es` + `jsencrypt` |
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `describeCryptoRuntime()` 可返回当前平台名、底层引擎名、是否支持高级分组模式、是否经过原生桥接
|
||||||
|
- Web 与微信小程序端的实现基于纯脚本运行时,App 端优先走原生桥接
|
||||||
|
- 文本加密、文本解密、摘要、签名、RSA 等接口均采用统一函数签名,便于业务跨端复用
|
||||||
|
|
||||||
|
## 安装与导入
|
||||||
|
|
||||||
|
将插件放入项目 `uni_modules` 目录后,直接从插件根目录导入:
|
||||||
|
|
||||||
|
```uts
|
||||||
|
import {
|
||||||
|
aesDecryptBytes,
|
||||||
|
aesDecryptText,
|
||||||
|
aesEncryptBytes,
|
||||||
|
aesEncryptText,
|
||||||
|
createRsaPair,
|
||||||
|
decodeText,
|
||||||
|
defaultByteCipherOptions,
|
||||||
|
defaultTextCipherOptions,
|
||||||
|
describeCryptoRuntime,
|
||||||
|
digestText,
|
||||||
|
encodeText,
|
||||||
|
rsaDecryptText,
|
||||||
|
rsaEncryptText,
|
||||||
|
signText
|
||||||
|
} from "@/uni_modules/laoqianjunzi-crypto"
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CryptoByteCipherOptions,
|
||||||
|
CryptoRsaPair,
|
||||||
|
CryptoRuntimeSnapshot,
|
||||||
|
CryptoTextCipherOptions
|
||||||
|
} from "@/uni_modules/laoqianjunzi-crypto"
|
||||||
|
```
|
||||||
|
|
||||||
|
请注意:
|
||||||
|
|
||||||
|
- 只从插件根目录导入,不要直接导入插件内部 `utssdk` 文件
|
||||||
|
- 业务代码建议统一使用插件根导出,便于后续维护和跨端类型识别
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
下面示例演示最常用的四类能力:运行时信息、文本编解码、摘要/HMAC、AES 文本加解密、RSA 密钥对与短文本加解密。
|
||||||
|
|
||||||
|
```uts
|
||||||
|
import {
|
||||||
|
aesDecryptText,
|
||||||
|
aesEncryptText,
|
||||||
|
createRsaPair,
|
||||||
|
decodeText,
|
||||||
|
defaultTextCipherOptions,
|
||||||
|
describeCryptoRuntime,
|
||||||
|
digestText,
|
||||||
|
encodeText,
|
||||||
|
rsaDecryptText,
|
||||||
|
rsaEncryptText,
|
||||||
|
signText
|
||||||
|
} from "@/uni_modules/laoqianjunzi-crypto"
|
||||||
|
|
||||||
|
import type {
|
||||||
|
CryptoRsaPair,
|
||||||
|
CryptoRuntimeSnapshot,
|
||||||
|
CryptoTextCipherOptions
|
||||||
|
} from "@/uni_modules/laoqianjunzi-crypto"
|
||||||
|
|
||||||
|
function runCryptoDemo(): void {
|
||||||
|
const runtime: CryptoRuntimeSnapshot = describeCryptoRuntime()
|
||||||
|
console.log("runtime", runtime.platformName, runtime.engineName)
|
||||||
|
|
||||||
|
const plainText = "Hello uni-app x"
|
||||||
|
const secretText = "1234567890abcdef"
|
||||||
|
const ivText = "fedcba0987654321"
|
||||||
|
|
||||||
|
const base64Text = encodeText("base64", plainText)
|
||||||
|
const restoreText = decodeText("base64", base64Text)
|
||||||
|
console.log("base64", base64Text)
|
||||||
|
console.log("restore", restoreText)
|
||||||
|
|
||||||
|
const sha256Text = digestText("sha256", plainText)
|
||||||
|
const hmacText = signText("sha256", "sign-key", plainText)
|
||||||
|
console.log("sha256", sha256Text)
|
||||||
|
console.log("hmac", hmacText)
|
||||||
|
|
||||||
|
const aesOptions: CryptoTextCipherOptions = defaultTextCipherOptions()
|
||||||
|
aesOptions.mode = "CBC"
|
||||||
|
aesOptions.padding = "PKCS7"
|
||||||
|
aesOptions.ivText = ivText
|
||||||
|
aesOptions.keyLength = 16
|
||||||
|
|
||||||
|
const aesCipherText = aesEncryptText(secretText, plainText, aesOptions)
|
||||||
|
const aesPlainText = aesDecryptText(secretText, aesCipherText, aesOptions)
|
||||||
|
console.log("aes cipher", aesCipherText)
|
||||||
|
console.log("aes plain", aesPlainText)
|
||||||
|
|
||||||
|
const rsaPair: CryptoRsaPair = createRsaPair(2048)
|
||||||
|
const rsaCipherText = rsaEncryptText(rsaPair.publicKey, plainText, "base64")
|
||||||
|
const rsaPlainText = rsaDecryptText(rsaPair.privateKey, rsaCipherText, "base64")
|
||||||
|
console.log("rsa plain", rsaPlainText)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## AES 字节数组示例
|
||||||
|
|
||||||
|
如果你的业务处理的是二进制数据,而不是普通字符串,建议使用字节接口:
|
||||||
|
|
||||||
|
```uts
|
||||||
|
import {
|
||||||
|
aesDecryptBytes,
|
||||||
|
aesEncryptBytes,
|
||||||
|
defaultByteCipherOptions
|
||||||
|
} from "@/uni_modules/laoqianjunzi-crypto"
|
||||||
|
|
||||||
|
import type { CryptoByteCipherOptions } from "@/uni_modules/laoqianjunzi-crypto"
|
||||||
|
|
||||||
|
function textToBytes(input: string): Uint8Array {
|
||||||
|
const output = new Uint8Array(input.length)
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
const code = input.charCodeAt(index)
|
||||||
|
output[index] = code == null ? 0 : code & 0xff
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToText(input: Uint8Array): string {
|
||||||
|
let output = ""
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
output += String.fromCharCode(input[index])
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function runByteCipherDemo(): void {
|
||||||
|
const options: CryptoByteCipherOptions = defaultByteCipherOptions()
|
||||||
|
options.mode = "CTR"
|
||||||
|
options.padding = "PKCS7"
|
||||||
|
options.keyLength = 16
|
||||||
|
|
||||||
|
const secretBytes = textToBytes("1234567890abcdef")
|
||||||
|
const plainBytes = textToBytes("hello-bytes")
|
||||||
|
|
||||||
|
const cipherBytes = aesEncryptBytes(secretBytes, plainBytes, options)
|
||||||
|
const restoreBytes = aesDecryptBytes(secretBytes, cipherBytes, options)
|
||||||
|
console.log("byte plain", bytesToText(restoreBytes))
|
||||||
|
console.log("cipher length", cipherBytes.length)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 对外类型
|
||||||
|
|
||||||
|
### CryptoDigestKind
|
||||||
|
|
||||||
|
摘要算法枚举:
|
||||||
|
|
||||||
|
- `md5`
|
||||||
|
- `sha1`
|
||||||
|
- `sha256`
|
||||||
|
- `sha512`
|
||||||
|
|
||||||
|
### CryptoMacKind
|
||||||
|
|
||||||
|
HMAC 算法枚举:
|
||||||
|
|
||||||
|
- `sha1`
|
||||||
|
- `sha256`
|
||||||
|
- `sha512`
|
||||||
|
|
||||||
|
### CryptoCodecKind
|
||||||
|
|
||||||
|
文本编解码枚举:
|
||||||
|
|
||||||
|
- `utf8`
|
||||||
|
- `hex`
|
||||||
|
- `base64`
|
||||||
|
- `base64url`
|
||||||
|
- `latin1`
|
||||||
|
- `utf16`
|
||||||
|
|
||||||
|
### CryptoBlockMode
|
||||||
|
|
||||||
|
分组模式枚举:
|
||||||
|
|
||||||
|
- `ECB`
|
||||||
|
- `CBC`
|
||||||
|
- `CFB`
|
||||||
|
- `CTR`
|
||||||
|
- `CTRGladman`
|
||||||
|
- `OFB`
|
||||||
|
|
||||||
|
### CryptoPaddingKind
|
||||||
|
|
||||||
|
填充模式枚举:
|
||||||
|
|
||||||
|
- `PKCS7`
|
||||||
|
- `ANSI_X923`
|
||||||
|
- `ISO_10126`
|
||||||
|
- `ISO_97971`
|
||||||
|
- `NONE`
|
||||||
|
- `ZERO`
|
||||||
|
|
||||||
|
### RsaOutputKind
|
||||||
|
|
||||||
|
RSA 密文输出格式:
|
||||||
|
|
||||||
|
- `base64`
|
||||||
|
- `hex`
|
||||||
|
|
||||||
|
### CryptoTextCipherOptions
|
||||||
|
|
||||||
|
AES 文本加解密参数:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `mode` | `CryptoBlockMode` | 是 | 分组模式 |
|
||||||
|
| `padding` | `CryptoPaddingKind` | 是 | 填充模式 |
|
||||||
|
| `ivText` | `string \\| null` | 是 | 初始向量文本,`ECB` 模式可传 `null` |
|
||||||
|
| `keyLength` | `number \\| null` | 是 | AES 密钥长度,建议传 `16`、`24`、`32` |
|
||||||
|
|
||||||
|
### CryptoByteCipherOptions
|
||||||
|
|
||||||
|
AES 字节加解密参数:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `mode` | `CryptoBlockMode` | 是 | 分组模式 |
|
||||||
|
| `padding` | `CryptoPaddingKind` | 是 | 填充模式 |
|
||||||
|
| `ivBytes` | `Uint8Array \\| null` | 是 | 初始向量字节数组,`ECB` 模式可传 `null` |
|
||||||
|
| `keyLength` | `number \\| null` | 是 | AES 密钥长度,建议传 `16`、`24`、`32` |
|
||||||
|
|
||||||
|
### CryptoTripleDesOptions
|
||||||
|
|
||||||
|
3DES 参数:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `mode` | `"ECB" \\| "CBC"` | 是 | 3DES 当前支持的分组模式 |
|
||||||
|
| `ivText` | `string \\| null` | 是 | 初始向量文本,`ECB` 模式可传 `null` |
|
||||||
|
|
||||||
|
### CryptoDesOptions
|
||||||
|
|
||||||
|
DES 参数:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `mode` | `CryptoBlockMode` | 是 | 分组模式 |
|
||||||
|
| `padding` | `CryptoPaddingKind` | 是 | 填充模式 |
|
||||||
|
| `ivText` | `string \\| null` | 是 | 初始向量文本,`ECB` 模式可传 `null` |
|
||||||
|
|
||||||
|
### CryptoRsaPair
|
||||||
|
|
||||||
|
RSA 密钥对对象:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `publicKey` | `string` | 是 | 公钥 PEM 文本 |
|
||||||
|
| `privateKey` | `string` | 是 | 私钥 PEM 文本 |
|
||||||
|
|
||||||
|
### CryptoRuntimeSnapshot
|
||||||
|
|
||||||
|
运行时描述对象:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `platformName` | `string` | 是 | 平台名称 |
|
||||||
|
| `engineName` | `string` | 是 | 当前底层实现名称 |
|
||||||
|
| `supportsAdvancedModes` | `boolean` | 是 | 是否支持高级分组模式 |
|
||||||
|
| `usesNativeBridge` | `boolean` | 是 | 是否经过原生桥接 |
|
||||||
|
|
||||||
|
## 默认参数函数
|
||||||
|
|
||||||
|
### defaultTextCipherOptions()
|
||||||
|
|
||||||
|
返回 AES 文本加解密默认参数:
|
||||||
|
|
||||||
|
| 字段 | 默认值 |
|
||||||
|
| --- | --- |
|
||||||
|
| `mode` | `CBC` |
|
||||||
|
| `padding` | `PKCS7` |
|
||||||
|
| `ivText` | `0123456789abcdef` |
|
||||||
|
| `keyLength` | `16` |
|
||||||
|
|
||||||
|
### defaultByteCipherOptions()
|
||||||
|
|
||||||
|
返回 AES 字节加解密默认参数:
|
||||||
|
|
||||||
|
| 字段 | 默认值 |
|
||||||
|
| --- | --- |
|
||||||
|
| `mode` | `CBC` |
|
||||||
|
| `padding` | `PKCS7` |
|
||||||
|
| `ivBytes` | `0123456789abcdef` 的 ASCII 字节 |
|
||||||
|
| `keyLength` | `16` |
|
||||||
|
|
||||||
|
### defaultTripleDesOptions()
|
||||||
|
|
||||||
|
返回 3DES 默认参数:
|
||||||
|
|
||||||
|
| 字段 | 默认值 |
|
||||||
|
| --- | --- |
|
||||||
|
| `mode` | `CBC` |
|
||||||
|
| `ivText` | `12345678` |
|
||||||
|
|
||||||
|
### defaultDesOptions()
|
||||||
|
|
||||||
|
返回 DES 默认参数:
|
||||||
|
|
||||||
|
| 字段 | 默认值 |
|
||||||
|
| --- | --- |
|
||||||
|
| `mode` | `CBC` |
|
||||||
|
| `padding` | `PKCS7` |
|
||||||
|
| `ivText` | `12345678` |
|
||||||
|
|
||||||
|
## 运行时信息函数
|
||||||
|
|
||||||
|
### describeCryptoRuntime()
|
||||||
|
|
||||||
|
返回当前平台的运行时信息:
|
||||||
|
|
||||||
|
```uts
|
||||||
|
const runtime = describeCryptoRuntime()
|
||||||
|
console.log(runtime.platformName)
|
||||||
|
console.log(runtime.engineName)
|
||||||
|
console.log(runtime.supportsAdvancedModes)
|
||||||
|
console.log(runtime.usesNativeBridge)
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 清单
|
||||||
|
|
||||||
|
### 编解码
|
||||||
|
|
||||||
|
| 函数 | 说明 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `encodeText(codec, plainText)` | 将普通文本编码为指定格式文本 | `string` |
|
||||||
|
| `decodeText(codec, encodedText)` | 将指定格式文本还原为普通文本 | `string` |
|
||||||
|
|
||||||
|
### 摘要与签名
|
||||||
|
|
||||||
|
| 函数 | 说明 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `digestText(kind, plainText)` | 计算摘要,结果为十六进制字符串 | `string` |
|
||||||
|
| `signText(kind, secretText, plainText)` | 计算 HMAC,结果为十六进制字符串 | `string` |
|
||||||
|
|
||||||
|
### AES 文本加解密
|
||||||
|
|
||||||
|
| 函数 | 说明 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `aesEncryptText(secretText, plainText, options)` | 使用文本密钥加密文本 | `string` |
|
||||||
|
| `aesDecryptText(secretText, cipherText, options)` | 使用文本密钥解密文本 | `string` |
|
||||||
|
|
||||||
|
### AES 字节加解密
|
||||||
|
|
||||||
|
| 函数 | 说明 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `aesEncryptBytes(secretBytes, plainBytes, options)` | 使用字节密钥加密字节数组 | `Uint8Array` |
|
||||||
|
| `aesDecryptBytes(secretBytes, cipherBytes, options)` | 使用字节密钥解密字节数组 | `Uint8Array` |
|
||||||
|
|
||||||
|
### 3DES
|
||||||
|
|
||||||
|
| 函数 | 说明 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `tripleDesEncryptText(secretText, plainText, options)` | 3DES 文本加密 | `string` |
|
||||||
|
| `tripleDesDecryptText(secretText, cipherText, options)` | 3DES 文本解密 | `string` |
|
||||||
|
|
||||||
|
### DES
|
||||||
|
|
||||||
|
| 函数 | 说明 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `desEncryptText(secretText, plainText, options)` | DES 文本加密 | `string` |
|
||||||
|
| `desDecryptText(secretText, cipherText, options)` | DES 文本解密 | `string` |
|
||||||
|
|
||||||
|
### RC4
|
||||||
|
|
||||||
|
| 函数 | 说明 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `rc4EncryptText(secretText, plainText)` | RC4 加密,返回十六进制密文 | `string` |
|
||||||
|
| `rc4DecryptText(secretText, cipherHexText)` | RC4 解密十六进制密文 | `string` |
|
||||||
|
|
||||||
|
### RSA
|
||||||
|
|
||||||
|
| 函数 | 说明 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `createRsaPair(keySize)` | 生成 RSA 密钥对 | `CryptoRsaPair` |
|
||||||
|
| `rsaEncryptText(publicKey, plainText, outputKind)` | 使用公钥加密短文本 | `string` |
|
||||||
|
| `rsaDecryptText(privateKey, cipherText, outputKind)` | 使用私钥解密短文本 | `string` |
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- `keySize` 推荐使用 `1024`、`2048`、`3072`、`4096`
|
||||||
|
- 一般业务默认建议使用 `2048`
|
||||||
|
|
||||||
|
## 使用建议
|
||||||
|
|
||||||
|
- 编解码、摘要、HMAC 建议直接使用字符串接口,返回值稳定、便于日志记录和接口传输
|
||||||
|
- 处理图片片段、协议报文、蓝牙包、文件块等二进制内容时,优先使用 `aesEncryptBytes` 与 `aesDecryptBytes`
|
||||||
|
- AES 跨端调用时,`keyLength` 建议固定传 `16`、`24`、`32` 之一,避免不同运行时对推断长度的理解差异
|
||||||
|
- 使用 `CBC`、`CFB`、`CTR`、`OFB` 等模式时,建议显式传入业务自定义 `ivText` 或 `ivBytes`
|
||||||
|
- `RSA` 更适合短文本、密钥交换或签名相关前置处理,不适合大体积正文直接加解密
|
||||||
|
- `RC4`、`DES`、`3DES` 更适用于兼容特定协议场景,新业务优先建议选择 `AES` 或 `RSA`
|
||||||
|
|
||||||
|
## 异常处理建议
|
||||||
|
|
||||||
|
参数不合法、密文格式不正确、密钥与向量不匹配时,接口可能抛出错误。业务代码建议统一使用 `try/catch` 包裹:
|
||||||
|
|
||||||
|
```uts
|
||||||
|
import {
|
||||||
|
aesEncryptText,
|
||||||
|
defaultTextCipherOptions
|
||||||
|
} from "@/uni_modules/laoqianjunzi-crypto"
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = defaultTextCipherOptions()
|
||||||
|
const cipherText = aesEncryptText("1234567890abcdef", "hello", options)
|
||||||
|
console.log(cipherText)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("crypto error", error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 本插件面向 `uni-app x`,不面向传统 `uni-app` 页面运行时
|
||||||
|
- 文本接口默认适合 UTF-8 文本场景;如果你的原始数据本身不是普通文本,请优先使用字节接口
|
||||||
|
- `ECB` 模式不依赖 IV,其余分组模式建议始终显式设置 IV
|
||||||
|
- RSA 密文输出格式由 `outputKind` 控制,发送方与接收方应保持一致
|
||||||
|
- 通过 `default*Options()` 获取参数对象后,可按当前业务需求覆盖默认值
|
||||||
|
|
||||||
|
## 示例页面
|
||||||
|
|
||||||
|
插件内已提供示例页面,可用于快速验证能力是否正常:
|
||||||
|
|
||||||
|
- `uni_modules/laoqianjunzi-crypto/pages/index`
|
||||||
|
|
||||||
|
如果项目已在 `pages.json` 中注册该页面,可直接运行到对应页面进行体验。
|
||||||
@@ -0,0 +1,744 @@
|
|||||||
|
package uts.sdk.modules.laoqianjunziCrypto
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.security.GeneralSecurityException
|
||||||
|
import java.security.KeyFactory
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.interfaces.RSAPrivateKey
|
||||||
|
import java.security.interfaces.RSAPublicKey
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
|
import java.security.spec.X509EncodedKeySpec
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
data class NativeRsaBundle(val publicKey: String, val privateKey: String)
|
||||||
|
|
||||||
|
object NativeCipherCore {
|
||||||
|
private const val AES_BLOCK_SIZE = 16
|
||||||
|
private const val DES_BLOCK_SIZE = 8
|
||||||
|
private val secureRandom = SecureRandom()
|
||||||
|
|
||||||
|
fun digestHex(kind: String, plainText: String): String {
|
||||||
|
return try {
|
||||||
|
val digest = MessageDigest.getInstance(resolveDigestAlgorithm(kind))
|
||||||
|
toHex(digest.digest(plainText.toByteArray(StandardCharsets.UTF_8)))
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException("摘要失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hmacHex(kind: String, secretText: String, plainText: String): String {
|
||||||
|
return try {
|
||||||
|
val mac = Mac.getInstance(resolveHmacAlgorithm(kind))
|
||||||
|
mac.init(SecretKeySpec(secretText.toByteArray(StandardCharsets.UTF_8), mac.algorithm))
|
||||||
|
toHex(mac.doFinal(plainText.toByteArray(StandardCharsets.UTF_8)))
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException("HMAC失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encodeBase64(plainText: String): String {
|
||||||
|
return Base64.encodeToString(plainText.toByteArray(StandardCharsets.UTF_8), Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeBase64(encodedText: String): String {
|
||||||
|
return try {
|
||||||
|
String(Base64.decode(encodedText, Base64.DEFAULT), StandardCharsets.UTF_8)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalArgumentException("Base64无效")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encodeCodec(codec: String, plainText: String): String {
|
||||||
|
val bytes = plainText.toByteArray(StandardCharsets.UTF_8)
|
||||||
|
return when (codec.lowercase()) {
|
||||||
|
"utf8" -> plainText
|
||||||
|
"hex" -> toHex(bytes)
|
||||||
|
"base64" -> Base64.encodeToString(bytes, Base64.NO_WRAP)
|
||||||
|
"base64url" -> Base64.encodeToString(bytes, Base64.NO_WRAP)
|
||||||
|
.replace('+', '-')
|
||||||
|
.replace('/', '_')
|
||||||
|
.trimEnd('=')
|
||||||
|
"latin1" -> latin1FromBytes(bytes)
|
||||||
|
"utf16" -> utf16CodecFromBytes(bytes)
|
||||||
|
else -> throw IllegalArgumentException("不支持的编码类型")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeCodec(codec: String, encodedText: String): String {
|
||||||
|
return when (codec.lowercase()) {
|
||||||
|
"utf8" -> encodedText
|
||||||
|
"hex" -> String(fromHex(encodedText), StandardCharsets.UTF_8)
|
||||||
|
"base64" -> decodeBase64(encodedText)
|
||||||
|
"base64url" -> {
|
||||||
|
var normalized = encodedText.replace('-', '+').replace('_', '/')
|
||||||
|
while (normalized.length % 4 != 0) {
|
||||||
|
normalized += '='
|
||||||
|
}
|
||||||
|
decodeBase64(normalized)
|
||||||
|
}
|
||||||
|
"latin1" -> String(bytesFromLatin1(encodedText), StandardCharsets.UTF_8)
|
||||||
|
"utf16" -> String(bytesFromUtf16Codec(encodedText), StandardCharsets.UTF_8)
|
||||||
|
else -> throw IllegalArgumentException("不支持的编码类型")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aesEncryptText(
|
||||||
|
secretText: String,
|
||||||
|
plainText: String,
|
||||||
|
mode: String,
|
||||||
|
padding: String,
|
||||||
|
ivText: String?,
|
||||||
|
keyLength: Int?
|
||||||
|
): String {
|
||||||
|
val cipherBytes = aesEncryptBytes(
|
||||||
|
secretText.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
plainText.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
mode,
|
||||||
|
padding,
|
||||||
|
ivText?.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
keyLength
|
||||||
|
)
|
||||||
|
return Base64.encodeToString(cipherBytes, Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aesDecryptText(
|
||||||
|
secretText: String,
|
||||||
|
cipherText: String,
|
||||||
|
mode: String,
|
||||||
|
padding: String,
|
||||||
|
ivText: String?,
|
||||||
|
keyLength: Int?
|
||||||
|
): String {
|
||||||
|
val cipherBytes = try {
|
||||||
|
Base64.decode(cipherText, Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalArgumentException("密文Base64无效")
|
||||||
|
}
|
||||||
|
val plainBytes = aesDecryptBytes(
|
||||||
|
secretText.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
cipherBytes,
|
||||||
|
mode,
|
||||||
|
padding,
|
||||||
|
ivText?.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
keyLength
|
||||||
|
)
|
||||||
|
return String(plainBytes, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aesEncryptBytes(
|
||||||
|
secretBytes: ByteArray,
|
||||||
|
plainBytes: ByteArray,
|
||||||
|
mode: String,
|
||||||
|
padding: String,
|
||||||
|
ivBytes: ByteArray?,
|
||||||
|
keyLength: Int?
|
||||||
|
): ByteArray {
|
||||||
|
return cryptAes(true, secretBytes, plainBytes, mode, padding, ivBytes, keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aesDecryptBytes(
|
||||||
|
secretBytes: ByteArray,
|
||||||
|
cipherBytes: ByteArray,
|
||||||
|
mode: String,
|
||||||
|
padding: String,
|
||||||
|
ivBytes: ByteArray?,
|
||||||
|
keyLength: Int?
|
||||||
|
): ByteArray {
|
||||||
|
return cryptAes(false, secretBytes, cipherBytes, mode, padding, ivBytes, keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tripleDesEncryptText(secretText: String, plainText: String, mode: String, ivText: String?): String {
|
||||||
|
val encrypted = cryptTripleDes(
|
||||||
|
true,
|
||||||
|
secretText.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
plainText.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
mode,
|
||||||
|
ivText?.toByteArray(StandardCharsets.UTF_8)
|
||||||
|
)
|
||||||
|
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tripleDesDecryptText(secretText: String, cipherText: String, mode: String, ivText: String?): String {
|
||||||
|
val cipherBytes = try {
|
||||||
|
Base64.decode(cipherText, Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalArgumentException("密文Base64无效")
|
||||||
|
}
|
||||||
|
val decrypted = cryptTripleDes(
|
||||||
|
false,
|
||||||
|
secretText.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
cipherBytes,
|
||||||
|
mode,
|
||||||
|
ivText?.toByteArray(StandardCharsets.UTF_8)
|
||||||
|
)
|
||||||
|
return String(decrypted, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun desEncryptText(secretText: String, plainText: String, mode: String, padding: String, ivText: String?): String {
|
||||||
|
val encrypted = cryptDes(
|
||||||
|
true,
|
||||||
|
secretText.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
plainText.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
mode,
|
||||||
|
padding,
|
||||||
|
ivText?.toByteArray(StandardCharsets.UTF_8)
|
||||||
|
)
|
||||||
|
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun desDecryptText(secretText: String, cipherText: String, mode: String, padding: String, ivText: String?): String {
|
||||||
|
val cipherBytes = try {
|
||||||
|
Base64.decode(cipherText, Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalArgumentException("密文Base64无效")
|
||||||
|
}
|
||||||
|
val decrypted = cryptDes(
|
||||||
|
false,
|
||||||
|
secretText.toByteArray(StandardCharsets.UTF_8),
|
||||||
|
cipherBytes,
|
||||||
|
mode,
|
||||||
|
padding,
|
||||||
|
ivText?.toByteArray(StandardCharsets.UTF_8)
|
||||||
|
)
|
||||||
|
return String(decrypted, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rc4EncryptToHex(secretText: String, plainText: String): String {
|
||||||
|
return toHex(rc4(secretText.toByteArray(StandardCharsets.UTF_8), plainText.toByteArray(StandardCharsets.UTF_8)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rc4DecryptFromHex(secretText: String, cipherHexText: String): String {
|
||||||
|
val plainBytes = rc4(secretText.toByteArray(StandardCharsets.UTF_8), fromHex(cipherHexText))
|
||||||
|
return String(plainBytes, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createRsaBundle(keySize: Int): NativeRsaBundle {
|
||||||
|
return try {
|
||||||
|
val generator = KeyPairGenerator.getInstance("RSA")
|
||||||
|
generator.initialize(keySize)
|
||||||
|
val pair = generator.generateKeyPair()
|
||||||
|
val publicPem = toPem("PUBLIC KEY", pair.public.encoded)
|
||||||
|
val privatePem = toPem("PRIVATE KEY", pair.private.encoded)
|
||||||
|
NativeRsaBundle(publicPem, privatePem)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException("RSA生成失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rsaEncrypt(publicKey: String, plainText: String, outputKind: String?): String {
|
||||||
|
return try {
|
||||||
|
val rsaKey = parsePublicKey(publicKey)
|
||||||
|
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, rsaKey)
|
||||||
|
val encrypted = rsaProcess(cipher, plainText.toByteArray(StandardCharsets.UTF_8), rsaEncryptChunkSize(rsaKey), "RSA加密失败")
|
||||||
|
if (isHexOutput(outputKind)) toHex(encrypted) else Base64.encodeToString(encrypted, Base64.NO_WRAP)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException("RSA加密失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rsaDecrypt(privateKey: String, cipherText: String, outputKind: String?): String {
|
||||||
|
return try {
|
||||||
|
val rsaKey = parsePrivateKey(privateKey)
|
||||||
|
val cipherBytes = if (isHexOutput(outputKind)) {
|
||||||
|
fromHex(cipherText)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Base64.decode(cipherText, Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalArgumentException("RSA密文无效")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, rsaKey)
|
||||||
|
val decrypted = rsaProcess(cipher, cipherBytes, rsaDecryptChunkSize(rsaKey), "RSA解密失败")
|
||||||
|
String(decrypted, StandardCharsets.UTF_8)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException("RSA解密失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cryptAes(
|
||||||
|
encrypt: Boolean,
|
||||||
|
secretBytes: ByteArray,
|
||||||
|
inputBytes: ByteArray,
|
||||||
|
mode: String,
|
||||||
|
padding: String,
|
||||||
|
ivBytes: ByteArray?,
|
||||||
|
keyLength: Int?
|
||||||
|
): ByteArray {
|
||||||
|
try {
|
||||||
|
val normalizedMode = resolveAesMode(mode)
|
||||||
|
val normalizedPadding = resolvePadding(padding)
|
||||||
|
val key = SecretKeySpec(normalizeAesKey(secretBytes, keyLength), "AES")
|
||||||
|
val data = if (encrypt) padBytes(inputBytes, AES_BLOCK_SIZE, normalizedPadding) else inputBytes
|
||||||
|
if (!encrypt && data.isEmpty()) {
|
||||||
|
return ByteArray(0)
|
||||||
|
}
|
||||||
|
val cipher = Cipher.getInstance("AES/$normalizedMode/NoPadding")
|
||||||
|
initCipher(cipher, encrypt, key, normalizedMode, normalizeIv(ivBytes, AES_BLOCK_SIZE, normalizedMode))
|
||||||
|
val output = cipher.doFinal(data)
|
||||||
|
return if (encrypt) output else unpadBytes(output, AES_BLOCK_SIZE, normalizedPadding)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: GeneralSecurityException) {
|
||||||
|
throw IllegalStateException(if (encrypt) "AES加密失败" else "AES解密失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cryptTripleDes(
|
||||||
|
encrypt: Boolean,
|
||||||
|
secretBytes: ByteArray,
|
||||||
|
inputBytes: ByteArray,
|
||||||
|
mode: String,
|
||||||
|
ivBytes: ByteArray?
|
||||||
|
): ByteArray {
|
||||||
|
try {
|
||||||
|
val normalizedMode = resolveTripleDesMode(mode)
|
||||||
|
val key = SecretKeySpec(normalizeTripleDesKey(secretBytes), "DESede")
|
||||||
|
val data = if (encrypt) padBytes(inputBytes, DES_BLOCK_SIZE, "PKCS7") else inputBytes
|
||||||
|
val cipher = Cipher.getInstance("DESede/$normalizedMode/NoPadding")
|
||||||
|
initCipher(cipher, encrypt, key, normalizedMode, normalizeIv(ivBytes, DES_BLOCK_SIZE, normalizedMode))
|
||||||
|
val output = cipher.doFinal(data)
|
||||||
|
return if (encrypt) output else unpadBytes(output, DES_BLOCK_SIZE, "PKCS7")
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: GeneralSecurityException) {
|
||||||
|
throw IllegalStateException(if (encrypt) "3DES加密失败" else "3DES解密失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cryptDes(
|
||||||
|
encrypt: Boolean,
|
||||||
|
secretBytes: ByteArray,
|
||||||
|
inputBytes: ByteArray,
|
||||||
|
mode: String,
|
||||||
|
padding: String,
|
||||||
|
ivBytes: ByteArray?
|
||||||
|
): ByteArray {
|
||||||
|
try {
|
||||||
|
val normalizedMode = resolveDesMode(mode)
|
||||||
|
val normalizedPadding = resolvePadding(padding)
|
||||||
|
val key = SecretKeySpec(normalizeDesKey(secretBytes), "DES")
|
||||||
|
val data = if (encrypt) padBytes(inputBytes, DES_BLOCK_SIZE, normalizedPadding) else inputBytes
|
||||||
|
if (!encrypt && data.isEmpty()) {
|
||||||
|
return ByteArray(0)
|
||||||
|
}
|
||||||
|
val cipher = Cipher.getInstance("DES/$normalizedMode/NoPadding")
|
||||||
|
initCipher(cipher, encrypt, key, normalizedMode, normalizeIv(ivBytes, DES_BLOCK_SIZE, normalizedMode))
|
||||||
|
val output = cipher.doFinal(data)
|
||||||
|
return if (encrypt) output else unpadBytes(output, DES_BLOCK_SIZE, normalizedPadding)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: GeneralSecurityException) {
|
||||||
|
throw IllegalStateException(if (encrypt) "DES加密失败" else "DES解密失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initCipher(cipher: Cipher, encrypt: Boolean, key: SecretKeySpec, mode: String, ivBytes: ByteArray?) {
|
||||||
|
if (mode == "ECB") {
|
||||||
|
cipher.init(if (encrypt) Cipher.ENCRYPT_MODE else Cipher.DECRYPT_MODE, key)
|
||||||
|
} else {
|
||||||
|
val actualIv = ivBytes ?: throw IllegalArgumentException("IV不能为空")
|
||||||
|
cipher.init(if (encrypt) Cipher.ENCRYPT_MODE else Cipher.DECRYPT_MODE, key, IvParameterSpec(actualIv))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveDigestAlgorithm(kind: String): String {
|
||||||
|
return when (kind.lowercase()) {
|
||||||
|
"md5" -> "MD5"
|
||||||
|
"sha1" -> "SHA-1"
|
||||||
|
"sha256" -> "SHA-256"
|
||||||
|
"sha512" -> "SHA-512"
|
||||||
|
else -> throw IllegalArgumentException("不支持的摘要算法")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveHmacAlgorithm(kind: String): String {
|
||||||
|
return when (kind.lowercase()) {
|
||||||
|
"sha1" -> "HmacSHA1"
|
||||||
|
"sha256" -> "HmacSHA256"
|
||||||
|
"sha512" -> "HmacSHA512"
|
||||||
|
else -> throw IllegalArgumentException("不支持的HMAC算法")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveAesMode(mode: String): String {
|
||||||
|
return when (mode.uppercase()) {
|
||||||
|
"ECB", "CBC", "CFB", "CTR", "OFB" -> mode.uppercase()
|
||||||
|
// CTRGladman 在 Android 端按标准 CTR 变换兼容映射处理。
|
||||||
|
"CTRGLADMAN" -> "CTR"
|
||||||
|
else -> throw IllegalArgumentException("不支持的AES模式")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveTripleDesMode(mode: String): String {
|
||||||
|
return when (mode.uppercase()) {
|
||||||
|
"ECB", "CBC" -> mode.uppercase()
|
||||||
|
else -> throw IllegalArgumentException("不支持的3DES模式")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveDesMode(mode: String): String {
|
||||||
|
return when (mode.uppercase()) {
|
||||||
|
"ECB", "CBC", "CFB", "CTR", "OFB" -> mode.uppercase()
|
||||||
|
"CTRGLADMAN" -> "CTR"
|
||||||
|
else -> throw IllegalArgumentException("不支持的DES模式")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolvePadding(padding: String): String {
|
||||||
|
return when (padding.uppercase()) {
|
||||||
|
"PKCS7", "ANSI_X923", "ISO_10126", "ISO_97971", "NONE", "ZERO" -> padding.uppercase()
|
||||||
|
else -> throw IllegalArgumentException("不支持的填充")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizeAesKey(secretBytes: ByteArray, keyLength: Int?): ByteArray {
|
||||||
|
val keySize = when (keyLength) {
|
||||||
|
null -> inferAesKeySize(secretBytes.size)
|
||||||
|
128, 192, 256 -> keyLength / 8
|
||||||
|
else -> throw IllegalArgumentException("AES密钥长度无效")
|
||||||
|
}
|
||||||
|
return fitBytes(secretBytes, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inferAesKeySize(size: Int): Int {
|
||||||
|
return when {
|
||||||
|
size <= 16 -> 16
|
||||||
|
size <= 24 -> 24
|
||||||
|
size <= 32 -> 32
|
||||||
|
else -> throw IllegalArgumentException("AES密钥长度无效")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizeTripleDesKey(secretBytes: ByteArray): ByteArray {
|
||||||
|
val source = fitBytes(secretBytes, 24)
|
||||||
|
if (secretBytes.size <= 16) {
|
||||||
|
System.arraycopy(source, 0, source, 16, 8)
|
||||||
|
}
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizeDesKey(secretBytes: ByteArray): ByteArray {
|
||||||
|
if (secretBytes.isEmpty()) {
|
||||||
|
throw IllegalArgumentException("密钥不能为空")
|
||||||
|
}
|
||||||
|
return fitBytes(secretBytes, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizeIv(ivBytes: ByteArray?, expectedSize: Int, mode: String): ByteArray? {
|
||||||
|
if (mode == "ECB") return null
|
||||||
|
val bytes = ivBytes ?: return null
|
||||||
|
return fitBytes(bytes, expectedSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fitBytes(source: ByteArray, targetSize: Int): ByteArray {
|
||||||
|
val output = ByteArray(targetSize)
|
||||||
|
val copySize = minOf(source.size, targetSize)
|
||||||
|
System.arraycopy(source, 0, output, 0, copySize)
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun padBytes(input: ByteArray, blockSize: Int, padding: String): ByteArray {
|
||||||
|
if (padding == "NONE") {
|
||||||
|
if (input.size % blockSize != 0) {
|
||||||
|
throw IllegalArgumentException("数据长度不是块大小整数倍")
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
val padSize = blockSize - (input.size % blockSize).let { if (it == 0) blockSize else it }
|
||||||
|
val actualPadSize = if (padSize == 0) blockSize else padSize
|
||||||
|
val output = ByteArray(input.size + actualPadSize)
|
||||||
|
System.arraycopy(input, 0, output, 0, input.size)
|
||||||
|
when (padding) {
|
||||||
|
"PKCS7" -> fillPad(output, input.size, actualPadSize, actualPadSize.toByte())
|
||||||
|
"ANSI_X923" -> output[output.lastIndex] = actualPadSize.toByte()
|
||||||
|
"ISO_10126" -> {
|
||||||
|
val randomBytes = ByteArray(actualPadSize - 1)
|
||||||
|
if (randomBytes.isNotEmpty()) {
|
||||||
|
secureRandom.nextBytes(randomBytes)
|
||||||
|
System.arraycopy(randomBytes, 0, output, input.size, randomBytes.size)
|
||||||
|
}
|
||||||
|
output[output.lastIndex] = actualPadSize.toByte()
|
||||||
|
}
|
||||||
|
"ISO_97971" -> {
|
||||||
|
output[input.size] = 0x80.toByte()
|
||||||
|
}
|
||||||
|
"ZERO" -> {
|
||||||
|
}
|
||||||
|
else -> throw IllegalArgumentException("不支持的填充")
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fillPad(output: ByteArray, start: Int, count: Int, value: Byte) {
|
||||||
|
for (i in start until start + count) {
|
||||||
|
output[i] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unpadBytes(input: ByteArray, blockSize: Int, padding: String): ByteArray {
|
||||||
|
if (padding == "NONE") {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
if (input.isEmpty() || input.size % blockSize != 0) {
|
||||||
|
throw IllegalArgumentException("密文长度无效")
|
||||||
|
}
|
||||||
|
val dataLength = when (padding) {
|
||||||
|
"PKCS7" -> {
|
||||||
|
val padSize = input.last().toInt() and 0xFF
|
||||||
|
validatePadSize(padSize, blockSize)
|
||||||
|
for (i in input.size - padSize until input.size) {
|
||||||
|
if ((input[i].toInt() and 0xFF) != padSize) {
|
||||||
|
throw IllegalArgumentException("填充无效")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input.size - padSize
|
||||||
|
}
|
||||||
|
"ANSI_X923" -> {
|
||||||
|
val padSize = input.last().toInt() and 0xFF
|
||||||
|
validatePadSize(padSize, blockSize)
|
||||||
|
for (i in input.size - padSize until input.lastIndex) {
|
||||||
|
if (input[i].toInt() != 0) {
|
||||||
|
throw IllegalArgumentException("填充无效")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input.size - padSize
|
||||||
|
}
|
||||||
|
"ISO_10126" -> {
|
||||||
|
val padSize = input.last().toInt() and 0xFF
|
||||||
|
validatePadSize(padSize, blockSize)
|
||||||
|
input.size - padSize
|
||||||
|
}
|
||||||
|
"ISO_97971" -> {
|
||||||
|
var index = input.lastIndex
|
||||||
|
while (index >= 0 && input[index].toInt() == 0) {
|
||||||
|
index -= 1
|
||||||
|
}
|
||||||
|
if (index < 0 || input[index] != 0x80.toByte()) {
|
||||||
|
throw IllegalArgumentException("填充无效")
|
||||||
|
}
|
||||||
|
index
|
||||||
|
}
|
||||||
|
"ZERO" -> {
|
||||||
|
var index = input.size
|
||||||
|
while (index > 0 && input[index - 1].toInt() == 0) {
|
||||||
|
index -= 1
|
||||||
|
}
|
||||||
|
index
|
||||||
|
}
|
||||||
|
else -> throw IllegalArgumentException("不支持的填充")
|
||||||
|
}
|
||||||
|
return input.copyOf(dataLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validatePadSize(padSize: Int, blockSize: Int) {
|
||||||
|
if (padSize <= 0 || padSize > blockSize) {
|
||||||
|
throw IllegalArgumentException("填充无效")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rc4(secretBytes: ByteArray, inputBytes: ByteArray): ByteArray {
|
||||||
|
if (secretBytes.isEmpty()) {
|
||||||
|
throw IllegalArgumentException("RC4密钥不能为空")
|
||||||
|
}
|
||||||
|
val s = IntArray(256) { it }
|
||||||
|
var j = 0
|
||||||
|
for (i in 0 until 256) {
|
||||||
|
j = (j + s[i] + (secretBytes[i % secretBytes.size].toInt() and 0xFF)) and 0xFF
|
||||||
|
val tmp = s[i]
|
||||||
|
s[i] = s[j]
|
||||||
|
s[j] = tmp
|
||||||
|
}
|
||||||
|
val output = ByteArray(inputBytes.size)
|
||||||
|
var i = 0
|
||||||
|
j = 0
|
||||||
|
for (index in inputBytes.indices) {
|
||||||
|
i = (i + 1) and 0xFF
|
||||||
|
j = (j + s[i]) and 0xFF
|
||||||
|
val tmp = s[i]
|
||||||
|
s[i] = s[j]
|
||||||
|
s[j] = tmp
|
||||||
|
val keyByte = s[(s[i] + s[j]) and 0xFF]
|
||||||
|
output[index] = (inputBytes[index].toInt() xor keyByte).toByte()
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parsePublicKey(publicKey: String): RSAPublicKey {
|
||||||
|
try {
|
||||||
|
val keyBytes = decodePem(publicKey, "PUBLIC KEY")
|
||||||
|
val keySpec = X509EncodedKeySpec(keyBytes)
|
||||||
|
return KeyFactory.getInstance("RSA").generatePublic(keySpec) as RSAPublicKey
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalArgumentException("公钥无效")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parsePrivateKey(privateKey: String): RSAPrivateKey {
|
||||||
|
try {
|
||||||
|
val keyBytes = decodePem(privateKey, "PRIVATE KEY")
|
||||||
|
val keySpec = PKCS8EncodedKeySpec(keyBytes)
|
||||||
|
return KeyFactory.getInstance("RSA").generatePrivate(keySpec) as RSAPrivateKey
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalArgumentException("私钥无效")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodePem(pemText: String, label: String): ByteArray {
|
||||||
|
val header = "-----BEGIN $label-----"
|
||||||
|
val footer = "-----END $label-----"
|
||||||
|
val trimmed = pemText.trim()
|
||||||
|
if (!trimmed.startsWith(header) || !trimmed.endsWith(footer)) {
|
||||||
|
throw IllegalArgumentException("PEM格式无效")
|
||||||
|
}
|
||||||
|
val content = trimmed
|
||||||
|
.removePrefix(header)
|
||||||
|
.removeSuffix(footer)
|
||||||
|
.replace("\\s".toRegex(), "")
|
||||||
|
return try {
|
||||||
|
Base64.decode(content, Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalArgumentException("PEM内容无效")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toPem(label: String, keyBytes: ByteArray): String {
|
||||||
|
val base64 = Base64.encodeToString(keyBytes, Base64.NO_WRAP)
|
||||||
|
val builder = StringBuilder()
|
||||||
|
builder.append("-----BEGIN ").append(label).append("-----\n")
|
||||||
|
var index = 0
|
||||||
|
while (index < base64.length) {
|
||||||
|
val end = minOf(index + 64, base64.length)
|
||||||
|
builder.append(base64, index, end).append('\n')
|
||||||
|
index = end
|
||||||
|
}
|
||||||
|
builder.append("-----END ").append(label).append("-----")
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rsaEncryptChunkSize(key: RSAPublicKey): Int {
|
||||||
|
return (key.modulus.bitLength() + 7) / 8 - 11
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rsaDecryptChunkSize(key: RSAPrivateKey): Int {
|
||||||
|
return (key.modulus.bitLength() + 7) / 8
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rsaProcess(cipher: Cipher, input: ByteArray, chunkSize: Int, failureMessage: String): ByteArray {
|
||||||
|
if (chunkSize <= 0) {
|
||||||
|
throw IllegalArgumentException("RSA密钥无效")
|
||||||
|
}
|
||||||
|
val output = ByteArrayOutputStream()
|
||||||
|
var offset = 0
|
||||||
|
try {
|
||||||
|
while (offset < input.size) {
|
||||||
|
val size = minOf(chunkSize, input.size - offset)
|
||||||
|
output.write(cipher.doFinal(input, offset, size))
|
||||||
|
offset += size
|
||||||
|
}
|
||||||
|
return output.toByteArray()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException(failureMessage)
|
||||||
|
} finally {
|
||||||
|
output.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isHexOutput(outputKind: String?): Boolean {
|
||||||
|
return outputKind?.lowercase() == "hex"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun latin1FromBytes(bytes: ByteArray): String {
|
||||||
|
val chars = CharArray(bytes.size)
|
||||||
|
for (i in bytes.indices) {
|
||||||
|
chars[i] = (bytes[i].toInt() and 0xFF).toChar()
|
||||||
|
}
|
||||||
|
return String(chars)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bytesFromLatin1(text: String): ByteArray {
|
||||||
|
val output = ByteArray(text.length)
|
||||||
|
for (i in text.indices) {
|
||||||
|
output[i] = (text[i].code and 0xFF).toByte()
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun utf16CodecFromBytes(bytes: ByteArray): String {
|
||||||
|
if (bytes.isEmpty()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
val chars = CharArray((bytes.size + 1) / 2)
|
||||||
|
var byteIndex = 0
|
||||||
|
var charIndex = 0
|
||||||
|
while (byteIndex < bytes.size) {
|
||||||
|
val high = bytes[byteIndex].toInt() and 0xFF
|
||||||
|
val low = if (byteIndex + 1 < bytes.size) bytes[byteIndex + 1].toInt() and 0xFF else 0
|
||||||
|
chars[charIndex] = ((high shl 8) or low).toChar()
|
||||||
|
byteIndex += 2
|
||||||
|
charIndex += 1
|
||||||
|
}
|
||||||
|
return String(chars)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bytesFromUtf16Codec(text: String): ByteArray {
|
||||||
|
val output = ByteArray(text.length * 2)
|
||||||
|
var index = 0
|
||||||
|
for (charValue in text) {
|
||||||
|
val code = charValue.code
|
||||||
|
output[index] = ((code ushr 8) and 0xFF).toByte()
|
||||||
|
output[index + 1] = (code and 0xFF).toByte()
|
||||||
|
index += 2
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toHex(bytes: ByteArray): String {
|
||||||
|
val chars = CharArray(bytes.size * 2)
|
||||||
|
val digits = "0123456789abcdef"
|
||||||
|
for (i in bytes.indices) {
|
||||||
|
val value = bytes[i].toInt() and 0xFF
|
||||||
|
chars[i * 2] = digits[value ushr 4]
|
||||||
|
chars[i * 2 + 1] = digits[value and 0x0F]
|
||||||
|
}
|
||||||
|
return String(chars)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fromHex(hexText: String): ByteArray {
|
||||||
|
val normalized = hexText.trim()
|
||||||
|
if (normalized.length % 2 != 0) {
|
||||||
|
throw IllegalArgumentException("Hex无效")
|
||||||
|
}
|
||||||
|
val output = ByteArray(normalized.length / 2)
|
||||||
|
for (i in output.indices) {
|
||||||
|
val start = i * 2
|
||||||
|
val value = normalized.substring(start, start + 2).toIntOrNull(16)
|
||||||
|
?: throw IllegalArgumentException("Hex无效")
|
||||||
|
output[i] = value.toByte()
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"minSdkVersion": "21"
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
import NativeCipherCore from 'uts.sdk.modules.laoqianjunziCrypto.NativeCipherCore'
|
||||||
|
import type { CryptoByteCipherOptions, CryptoCodecKind, CryptoDesOptions, CryptoDigestKind, CryptoMacKind, CryptoRsaPair, CryptoRuntimeSnapshot, CryptoTextCipherOptions, CryptoTripleDesOptions, RsaOutputKind } from '../interface.uts'
|
||||||
|
import { buildRuntimeSnapshot, createDefaultByteCipherOptions, createDefaultDesOptions, createDefaultTextCipherOptions, createDefaultTripleDesOptions, normalizeByteCipherOptions, normalizeDesOptions, normalizeTextCipherOptions, normalizeTripleDesOptions, resolveHexOutput } from '../shared/options.uts'
|
||||||
|
import type { CompatBridge, CompatCrypto } from '../shared/compat.uts'
|
||||||
|
import { aesDecryptBytesCompat, aesDecryptCompat, aesEncryptBytesCompat, aesEncryptCompat, base64DecodeCompat, base64EncodeCompat, createCompatBridge, createCompatCrypto, desDecryptCompat, desEncryptCompat, generateRsaKeyPairCompat, hmacSha1Compat, hmacSha256Compat, hmacSha512Compat, md5Compat, rc4DecryptCompat, rc4EncryptCompat, rsaDecryptCompat, rsaEncryptCompat, sha1Compat, sha256Compat, sha512Compat } from '../shared/compat.uts'
|
||||||
|
|
||||||
|
function toNativeBytes(source : Uint8Array) : ByteArray {
|
||||||
|
const output = new ByteArray(source.length.toInt())
|
||||||
|
let index = 0
|
||||||
|
while (index < source.length) {
|
||||||
|
output[index.toInt()] = source[index.toInt()].toByte()
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromNativeBytes(source : ByteArray) : Uint8Array {
|
||||||
|
const output = new Uint8Array(source.size)
|
||||||
|
let index = 0
|
||||||
|
while (index < source.size) {
|
||||||
|
output[index.toInt()] = source[index.toInt()] & 0xff
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function byteToHex(value : number) : string {
|
||||||
|
let part = value.toString(16)
|
||||||
|
if (part.length < 2) {
|
||||||
|
part = '0' + part
|
||||||
|
}
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultTextCipherOptions() : CryptoTextCipherOptions {
|
||||||
|
return createDefaultTextCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultByteCipherOptions() : CryptoByteCipherOptions {
|
||||||
|
return createDefaultByteCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultTripleDesOptions() : CryptoTripleDesOptions {
|
||||||
|
return createDefaultTripleDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultDesOptions() : CryptoDesOptions {
|
||||||
|
return createDefaultDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function describeCryptoRuntime() : CryptoRuntimeSnapshot {
|
||||||
|
return buildRuntimeSnapshot('Android', 'JCA', true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeText(codec : CryptoCodecKind, plainText : string) : string {
|
||||||
|
return NativeCipherCore.encodeCodec(codec, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeText(codec : CryptoCodecKind, encodedText : string) : string {
|
||||||
|
return NativeCipherCore.decodeCodec(codec, encodedText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function digestText(kind : CryptoDigestKind, plainText : string) : string {
|
||||||
|
return NativeCipherCore.digestHex(kind, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signText(kind : CryptoMacKind, secretText : string, plainText : string) : string {
|
||||||
|
return NativeCipherCore.hmacHex(kind, secretText, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptText(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
const actualKeyLength = actual.keyLength == null ? null : actual.keyLength.toInt()
|
||||||
|
return NativeCipherCore.aesEncryptText(secretText, plainText, actual.mode, actual.padding, actual.ivText, actualKeyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptText(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
const actualKeyLength = actual.keyLength == null ? null : actual.keyLength.toInt()
|
||||||
|
return NativeCipherCore.aesDecryptText(secretText, cipherText, actual.mode, actual.padding, actual.ivText, actualKeyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptBytes(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
const actualKeyLength = actual.keyLength == null ? null : actual.keyLength.toInt()
|
||||||
|
return fromNativeBytes(NativeCipherCore.aesEncryptBytes(toNativeBytes(secretBytes), toNativeBytes(plainBytes), actual.mode, actual.padding, actual.ivBytes == null ? null : toNativeBytes(actual.ivBytes), actualKeyLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptBytes(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
const actualKeyLength = actual.keyLength == null ? null : actual.keyLength.toInt()
|
||||||
|
return fromNativeBytes(NativeCipherCore.aesDecryptBytes(toNativeBytes(secretBytes), toNativeBytes(cipherBytes), actual.mode, actual.padding, actual.ivBytes == null ? null : toNativeBytes(actual.ivBytes), actualKeyLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesEncryptText(secretText : string, plainText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
return NativeCipherCore.tripleDesEncryptText(secretText, plainText, actual.mode, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesDecryptText(secretText : string, cipherText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
return NativeCipherCore.tripleDesDecryptText(secretText, cipherText, actual.mode, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncryptText(secretText : string, plainText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
return NativeCipherCore.desEncryptText(secretText, plainText, actual.mode, actual.padding, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecryptText(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
return NativeCipherCore.desDecryptText(secretText, cipherText, actual.mode, actual.padding, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4EncryptText(secretText : string, plainText : string) : string {
|
||||||
|
return NativeCipherCore.rc4EncryptToHex(secretText, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4DecryptText(secretText : string, cipherHexText : string) : string {
|
||||||
|
return NativeCipherCore.rc4DecryptFromHex(secretText, cipherHexText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRsaPair(keySize : number) : CryptoRsaPair {
|
||||||
|
const bundle = NativeCipherCore.createRsaBundle(keySize.toInt())
|
||||||
|
return {
|
||||||
|
publicKey: bundle.publicKey,
|
||||||
|
privateKey: bundle.privateKey
|
||||||
|
} as CryptoRsaPair
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncryptText(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return NativeCipherCore.rsaEncrypt(publicKey, plainText, resolveHexOutput(outputKind) ? 'hex' : 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecryptText(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return NativeCipherCore.rsaDecrypt(privateKey, cipherText, resolveHexOutput(outputKind) ? 'hex' : 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
function compatBridge() : CompatBridge {
|
||||||
|
return createCompatBridge(
|
||||||
|
(codec : CryptoCodecKind, plainText : string) : string => encodeText(codec, plainText),
|
||||||
|
(codec : CryptoCodecKind, encodedText : string) : string => decodeText(codec, encodedText),
|
||||||
|
(kind : CryptoDigestKind, plainText : string) : string => digestText(kind, plainText),
|
||||||
|
(kind : CryptoMacKind, secretText : string, plainText : string) : string => signText(kind, secretText, plainText),
|
||||||
|
(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string => aesEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string => aesDecryptText(secretText, cipherText, options),
|
||||||
|
(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesEncryptBytes(secretBytes, plainBytes, options),
|
||||||
|
(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesDecryptBytes(secretBytes, cipherBytes, options),
|
||||||
|
(secretText : string, plainText : string, options : CryptoDesOptions | null) : string => desEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string => desDecryptText(secretText, cipherText, options),
|
||||||
|
(keySize : number) : CryptoRsaPair => createRsaPair(keySize),
|
||||||
|
(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string => rsaEncryptText(publicKey, plainText, outputKind),
|
||||||
|
(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string => rsaDecryptText(privateKey, cipherText, outputKind),
|
||||||
|
(secretText : string, plainText : string) : string => rc4EncryptText(secretText, plainText),
|
||||||
|
(secretText : string, cipherHexText : string) : string => rc4DecryptText(secretText, cipherHexText)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Encode(input : string) : string {
|
||||||
|
return base64EncodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Decode(input : string) : string {
|
||||||
|
return base64DecodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function md5(input : string) : string {
|
||||||
|
return md5Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha1(input : string) : string {
|
||||||
|
return sha1Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha256(input : string) : string {
|
||||||
|
return sha256Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha512(input : string) : string {
|
||||||
|
return sha512Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha1(key : string, data : string) : string {
|
||||||
|
return hmacSha1Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha256(key : string, data : string) : string {
|
||||||
|
return hmacSha256Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha512(key : string, data : string) : string {
|
||||||
|
return hmacSha512Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesEncryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesDecryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesEncryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesDecryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desEncryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desDecryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateRSAKeyPair(keySize : number = 2048) : CryptoRsaPair {
|
||||||
|
return generateRsaKeyPairCompat(compatBridge(), keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncrypt(publicKey : string, data : string) : string {
|
||||||
|
return rsaEncryptCompat(compatBridge(), publicKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecrypt(privateKey : string, data : string) : string {
|
||||||
|
return rsaDecryptCompat(compatBridge(), privateKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Encrypt(key : string, data : string) : string {
|
||||||
|
return rc4EncryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Decrypt(key : string, data : string) : string {
|
||||||
|
return rc4DecryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCrypto() : CompatCrypto {
|
||||||
|
return createCompatCrypto(compatBridge())
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@ohos/crypto-js": "2.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,428 @@
|
|||||||
|
import type { CryptoByteCipherOptions, CryptoCodecKind, CryptoDesOptions, CryptoDigestKind, CryptoMacKind, CryptoRsaPair, CryptoRuntimeSnapshot, CryptoTextCipherOptions, CryptoTripleDesOptions, RsaOutputKind } from '../interface.uts'
|
||||||
|
import { buildRuntimeSnapshot, createDefaultByteCipherOptions, createDefaultDesOptions, createDefaultTextCipherOptions, createDefaultTripleDesOptions, normalizeByteCipherOptions, normalizeDesOptions, normalizeTextCipherOptions, normalizeTripleDesOptions, resolveHexOutput } from '../shared/options.uts'
|
||||||
|
import { NativeHarmonyRsaCore } from './native_rsa.ets'
|
||||||
|
import { CryptoJS } from '@ohos/crypto-js'
|
||||||
|
import type { CompatBridge, CompatCrypto } from '../shared/compat.uts'
|
||||||
|
import { aesDecryptBytesCompat, aesDecryptCompat, aesEncryptBytesCompat, aesEncryptCompat, base64DecodeCompat, base64EncodeCompat, createCompatBridge, createCompatCrypto, desDecryptCompat, desEncryptCompat, generateRsaKeyPairCompat, hmacSha1Compat, hmacSha256Compat, hmacSha512Compat, md5Compat, rc4DecryptCompat, rc4EncryptCompat, rsaDecryptCompat, rsaEncryptCompat, sha1Compat, sha256Compat, sha512Compat } from '../shared/compat.uts'
|
||||||
|
|
||||||
|
type WordValue = CryptoJS.lib.WordArray
|
||||||
|
|
||||||
|
function bytesToWordArray(input : Uint8Array) : WordValue {
|
||||||
|
const words = [] as number[]
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
let word = 0
|
||||||
|
let offset = 0
|
||||||
|
while (offset < 4 && index + offset < input.length) {
|
||||||
|
word |= input[index + offset] << (24 - offset * 8)
|
||||||
|
offset += 1
|
||||||
|
}
|
||||||
|
words.push(word)
|
||||||
|
index += 4
|
||||||
|
}
|
||||||
|
return CryptoJS.lib.WordArray.create(words, input.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function wordArrayToBytes(input : WordValue) : Uint8Array {
|
||||||
|
const output = new Uint8Array(input.sigBytes)
|
||||||
|
let index = 0
|
||||||
|
while (index < input.sigBytes) {
|
||||||
|
const wordIndex = Math.floor(index / 4)
|
||||||
|
const offset = index % 4
|
||||||
|
output[index] = (input.words[wordIndex] >>> (24 - offset * 8)) & 0xff
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAesMode(mode : string) : CryptoJS.BlockCipherMode {
|
||||||
|
if (mode == 'CBC') {
|
||||||
|
return CryptoJS.mode.CBC
|
||||||
|
}
|
||||||
|
if (mode == 'CFB') {
|
||||||
|
return CryptoJS.mode.CFB
|
||||||
|
}
|
||||||
|
if (mode == 'CTR') {
|
||||||
|
return CryptoJS.mode.CTR
|
||||||
|
}
|
||||||
|
if (mode == 'CTRGladman') {
|
||||||
|
return CryptoJS.mode.CTRGladman
|
||||||
|
}
|
||||||
|
if (mode == 'ECB') {
|
||||||
|
return CryptoJS.mode.ECB
|
||||||
|
}
|
||||||
|
return CryptoJS.mode.OFB
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePadding(padding : string) : CryptoJS.Padding {
|
||||||
|
if (padding == 'PKCS7') {
|
||||||
|
return CryptoJS.pad.Pkcs7
|
||||||
|
}
|
||||||
|
if (padding == 'ANSI_X923') {
|
||||||
|
return CryptoJS.pad.AnsiX923
|
||||||
|
}
|
||||||
|
if (padding == 'ISO_10126') {
|
||||||
|
return CryptoJS.pad.Iso10126
|
||||||
|
}
|
||||||
|
if (padding == 'ISO_97971') {
|
||||||
|
return CryptoJS.pad.Iso97971
|
||||||
|
}
|
||||||
|
if (padding == 'NONE') {
|
||||||
|
return CryptoJS.pad.NoPadding
|
||||||
|
}
|
||||||
|
return CryptoJS.pad.ZeroPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitIvText(ivText : string | null, size : number) : WordValue {
|
||||||
|
const bytes = new Uint8Array(size)
|
||||||
|
if (ivText != null) {
|
||||||
|
const parsed : WordValue = CryptoJS.enc.Utf8.parse(ivText)
|
||||||
|
const source = wordArrayToBytes(parsed)
|
||||||
|
let index = 0
|
||||||
|
while (index < size && index < source.length) {
|
||||||
|
bytes[index] = source[index]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytesToWordArray(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitIvBytes(ivBytes : Uint8Array | null, size : number) : WordValue {
|
||||||
|
const bytes = new Uint8Array(size)
|
||||||
|
if (ivBytes != null) {
|
||||||
|
let index = 0
|
||||||
|
while (index < size && index < ivBytes.length) {
|
||||||
|
bytes[index] = ivBytes[index]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytesToWordArray(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitKeyWordFromText(secretText : string, keyLength : number | null) : WordValue {
|
||||||
|
let target = 16
|
||||||
|
if (keyLength == 24 || keyLength == 32) {
|
||||||
|
target = keyLength
|
||||||
|
}
|
||||||
|
const sourceWord : WordValue = CryptoJS.enc.Utf8.parse(secretText)
|
||||||
|
const source = wordArrayToBytes(sourceWord)
|
||||||
|
const output = new Uint8Array(target)
|
||||||
|
let index = 0
|
||||||
|
while (index < target && index < source.length) {
|
||||||
|
output[index] = source[index]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return bytesToWordArray(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitKeyWordFromBytes(secretBytes : Uint8Array, keyLength : number | null) : WordValue {
|
||||||
|
let target = 16
|
||||||
|
if (keyLength == 24 || keyLength == 32) {
|
||||||
|
target = keyLength
|
||||||
|
}
|
||||||
|
const output = new Uint8Array(target)
|
||||||
|
let index = 0
|
||||||
|
while (index < target && index < secretBytes.length) {
|
||||||
|
output[index] = secretBytes[index]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return bytesToWordArray(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultTextCipherOptions() : CryptoTextCipherOptions {
|
||||||
|
return createDefaultTextCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultByteCipherOptions() : CryptoByteCipherOptions {
|
||||||
|
return createDefaultByteCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultTripleDesOptions() : CryptoTripleDesOptions {
|
||||||
|
return createDefaultTripleDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultDesOptions() : CryptoDesOptions {
|
||||||
|
return createDefaultDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function describeCryptoRuntime() : CryptoRuntimeSnapshot {
|
||||||
|
return buildRuntimeSnapshot('Harmony', '@ohos/crypto-js', true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeText(codec : CryptoCodecKind, plainText : string) : string {
|
||||||
|
if (codec == 'utf8') {
|
||||||
|
return plainText
|
||||||
|
}
|
||||||
|
const word : WordValue = CryptoJS.enc.Utf8.parse(plainText)
|
||||||
|
if (codec == 'hex') {
|
||||||
|
return CryptoJS.enc.Hex.stringify(word)
|
||||||
|
}
|
||||||
|
if (codec == 'base64') {
|
||||||
|
return CryptoJS.enc.Base64.stringify(word)
|
||||||
|
}
|
||||||
|
if (codec == 'base64url') {
|
||||||
|
return CryptoJS.enc.Base64url.stringify(word)
|
||||||
|
}
|
||||||
|
if (codec == 'latin1') {
|
||||||
|
return CryptoJS.enc.Latin1.stringify(word)
|
||||||
|
}
|
||||||
|
return CryptoJS.enc.Utf16.stringify(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeText(codec : CryptoCodecKind, encodedText : string) : string {
|
||||||
|
if (codec == 'utf8') {
|
||||||
|
return encodedText
|
||||||
|
}
|
||||||
|
if (codec == 'hex') {
|
||||||
|
return CryptoJS.enc.Hex.parse(encodedText).toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
if (codec == 'base64') {
|
||||||
|
return CryptoJS.enc.Base64.parse(encodedText).toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
if (codec == 'base64url') {
|
||||||
|
return CryptoJS.enc.Base64url.parse(encodedText).toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
if (codec == 'latin1') {
|
||||||
|
return CryptoJS.enc.Latin1.parse(encodedText).toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
return CryptoJS.enc.Utf16.parse(encodedText).toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function digestText(kind : CryptoDigestKind, plainText : string) : string {
|
||||||
|
if (kind == 'md5') {
|
||||||
|
return CryptoJS.MD5(plainText).toString()
|
||||||
|
}
|
||||||
|
if (kind == 'sha1') {
|
||||||
|
return CryptoJS.SHA1(plainText).toString()
|
||||||
|
}
|
||||||
|
if (kind == 'sha256') {
|
||||||
|
return CryptoJS.SHA256(plainText).toString()
|
||||||
|
}
|
||||||
|
return CryptoJS.SHA512(plainText).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signText(kind : CryptoMacKind, secretText : string, plainText : string) : string {
|
||||||
|
if (kind == 'sha1') {
|
||||||
|
return CryptoJS.HmacSHA1(plainText, secretText).toString()
|
||||||
|
}
|
||||||
|
if (kind == 'sha256') {
|
||||||
|
return CryptoJS.HmacSHA256(plainText, secretText).toString()
|
||||||
|
}
|
||||||
|
return CryptoJS.HmacSHA512(plainText, secretText).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptText(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
const encrypted : CryptoJS.lib.CipherParams = CryptoJS.AES.encrypt(plainText, fitKeyWordFromText(secretText, actual.keyLength), {
|
||||||
|
iv: fitIvText(actual.ivText, 16),
|
||||||
|
mode: resolveAesMode(actual.mode),
|
||||||
|
padding: resolvePadding(actual.padding)
|
||||||
|
})
|
||||||
|
return encrypted.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptText(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
const decrypted : WordValue = CryptoJS.AES.decrypt(cipherText, fitKeyWordFromText(secretText, actual.keyLength), {
|
||||||
|
iv: fitIvText(actual.ivText, 16),
|
||||||
|
mode: resolveAesMode(actual.mode),
|
||||||
|
padding: resolvePadding(actual.padding)
|
||||||
|
})
|
||||||
|
return decrypted.toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptBytes(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
const encrypted : CryptoJS.lib.CipherParams = CryptoJS.AES.encrypt(bytesToWordArray(plainBytes), fitKeyWordFromBytes(secretBytes, actual.keyLength), {
|
||||||
|
iv: fitIvBytes(actual.ivBytes, 16),
|
||||||
|
mode: resolveAesMode(actual.mode),
|
||||||
|
padding: resolvePadding(actual.padding)
|
||||||
|
})
|
||||||
|
return wordArrayToBytes(encrypted.ciphertext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptBytes(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
const payload : UTSJSONObject = {
|
||||||
|
ciphertext: bytesToWordArray(cipherBytes)
|
||||||
|
}
|
||||||
|
const decrypted : WordValue = CryptoJS.AES.decrypt(CryptoJS.lib.CipherParams.create(payload), fitKeyWordFromBytes(secretBytes, actual.keyLength), {
|
||||||
|
iv: fitIvBytes(actual.ivBytes, 16),
|
||||||
|
mode: resolveAesMode(actual.mode),
|
||||||
|
padding: resolvePadding(actual.padding)
|
||||||
|
})
|
||||||
|
return wordArrayToBytes(decrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesEncryptText(secretText : string, plainText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
const encrypted : CryptoJS.lib.CipherParams = CryptoJS.TripleDES.encrypt(plainText, fitKeyWordFromText(secretText, 24), {
|
||||||
|
iv: fitIvText(actual.ivText, 8),
|
||||||
|
mode: actual.mode == 'ECB' ? CryptoJS.mode.ECB : CryptoJS.mode.CBC,
|
||||||
|
padding: CryptoJS.pad.Pkcs7
|
||||||
|
})
|
||||||
|
return encrypted.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesDecryptText(secretText : string, cipherText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
const decrypted : WordValue = CryptoJS.TripleDES.decrypt(cipherText, fitKeyWordFromText(secretText, 24), {
|
||||||
|
iv: fitIvText(actual.ivText, 8),
|
||||||
|
mode: actual.mode == 'ECB' ? CryptoJS.mode.ECB : CryptoJS.mode.CBC,
|
||||||
|
padding: CryptoJS.pad.Pkcs7
|
||||||
|
})
|
||||||
|
return decrypted.toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncryptText(secretText : string, plainText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
const encrypted : CryptoJS.lib.CipherParams = CryptoJS.DES.encrypt(plainText, fitKeyWordFromText(secretText, 8), {
|
||||||
|
iv: fitIvText(actual.ivText, 8),
|
||||||
|
mode: resolveAesMode(actual.mode),
|
||||||
|
padding: resolvePadding(actual.padding)
|
||||||
|
})
|
||||||
|
return encrypted.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecryptText(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
const decrypted : WordValue = CryptoJS.DES.decrypt(cipherText, fitKeyWordFromText(secretText, 8), {
|
||||||
|
iv: fitIvText(actual.ivText, 8),
|
||||||
|
mode: resolveAesMode(actual.mode),
|
||||||
|
padding: resolvePadding(actual.padding)
|
||||||
|
})
|
||||||
|
return decrypted.toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4EncryptText(secretText : string, plainText : string) : string {
|
||||||
|
const encrypted : CryptoJS.lib.CipherParams = CryptoJS.RC4.encrypt(plainText, CryptoJS.enc.Utf8.parse(secretText))
|
||||||
|
return encrypted.ciphertext.toString(CryptoJS.enc.Hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4DecryptText(secretText : string, cipherHexText : string) : string {
|
||||||
|
const payload : UTSJSONObject = {
|
||||||
|
ciphertext: CryptoJS.enc.Hex.parse(cipherHexText)
|
||||||
|
}
|
||||||
|
const decrypted : WordValue = CryptoJS.RC4.decrypt(CryptoJS.lib.CipherParams.create(payload), CryptoJS.enc.Utf8.parse(secretText))
|
||||||
|
return decrypted.toString(CryptoJS.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRsaPair(keySize : number) : CryptoRsaPair {
|
||||||
|
const bundle = NativeHarmonyRsaCore.createRsaBundle(keySize)
|
||||||
|
return {
|
||||||
|
publicKey: bundle.publicKey,
|
||||||
|
privateKey: bundle.privateKey
|
||||||
|
} as CryptoRsaPair
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncryptText(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return NativeHarmonyRsaCore.rsaEncrypt(publicKey, plainText, resolveHexOutput(outputKind) ? 'hex' : 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecryptText(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return NativeHarmonyRsaCore.rsaDecrypt(privateKey, cipherText, resolveHexOutput(outputKind) ? 'hex' : 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
function compatBridge() : CompatBridge {
|
||||||
|
return createCompatBridge(
|
||||||
|
(codec : CryptoCodecKind, plainText : string) : string => encodeText(codec, plainText),
|
||||||
|
(codec : CryptoCodecKind, encodedText : string) : string => decodeText(codec, encodedText),
|
||||||
|
(kind : CryptoDigestKind, plainText : string) : string => digestText(kind, plainText),
|
||||||
|
(kind : CryptoMacKind, secretText : string, plainText : string) : string => signText(kind, secretText, plainText),
|
||||||
|
(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string => aesEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string => aesDecryptText(secretText, cipherText, options),
|
||||||
|
(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesEncryptBytes(secretBytes, plainBytes, options),
|
||||||
|
(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesDecryptBytes(secretBytes, cipherBytes, options),
|
||||||
|
(secretText : string, plainText : string, options : CryptoDesOptions | null) : string => desEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string => desDecryptText(secretText, cipherText, options),
|
||||||
|
(keySize : number) : CryptoRsaPair => createRsaPair(keySize),
|
||||||
|
(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string => rsaEncryptText(publicKey, plainText, outputKind),
|
||||||
|
(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string => rsaDecryptText(privateKey, cipherText, outputKind),
|
||||||
|
(secretText : string, plainText : string) : string => rc4EncryptText(secretText, plainText),
|
||||||
|
(secretText : string, cipherHexText : string) : string => rc4DecryptText(secretText, cipherHexText)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Encode(input : string) : string {
|
||||||
|
return base64EncodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Decode(input : string) : string {
|
||||||
|
return base64DecodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function md5(input : string) : string {
|
||||||
|
return md5Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha1(input : string) : string {
|
||||||
|
return sha1Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha256(input : string) : string {
|
||||||
|
return sha256Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha512(input : string) : string {
|
||||||
|
return sha512Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha1(key : string, data : string) : string {
|
||||||
|
return hmacSha1Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha256(key : string, data : string) : string {
|
||||||
|
return hmacSha256Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha512(key : string, data : string) : string {
|
||||||
|
return hmacSha512Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesEncryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesDecryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesEncryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesDecryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desEncryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desDecryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateRSAKeyPair(keySize : number = 2048) : CryptoRsaPair {
|
||||||
|
return generateRsaKeyPairCompat(compatBridge(), keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncrypt(publicKey : string, data : string) : string {
|
||||||
|
return rsaEncryptCompat(compatBridge(), publicKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecrypt(privateKey : string, data : string) : string {
|
||||||
|
return rsaDecryptCompat(compatBridge(), privateKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Encrypt(key : string, data : string) : string {
|
||||||
|
return rc4EncryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Decrypt(key : string, data : string) : string {
|
||||||
|
return rc4DecryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCrypto() : CompatCrypto {
|
||||||
|
return createCompatCrypto(compatBridge())
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import cryptoFramework from '@ohos.security.cryptoFramework'
|
||||||
|
import util from '@ohos.util'
|
||||||
|
|
||||||
|
const UTF8_ENCODER = new util.TextEncoder('utf-8')
|
||||||
|
const UTF8_DECODER = util.TextDecoder.create('utf-8')
|
||||||
|
const BASE64_HELPER = new util.Base64Helper()
|
||||||
|
|
||||||
|
const PUBLIC_PEM_FORMATS: string[] = ['X509', 'x509', 'SPKI', 'spki', 'PKCS1', 'pkcs1']
|
||||||
|
const PRIVATE_PEM_FORMATS: string[] = ['PKCS8', 'pkcs8', 'PKCS1', 'pkcs1']
|
||||||
|
const RSA_GENERATOR_ALGS: string[] = ['RSA']
|
||||||
|
const RSA_CIPHER_ALGS: string[] = [
|
||||||
|
'RSA/ECB/PKCS1Padding',
|
||||||
|
'RSA/None/PKCS1Padding',
|
||||||
|
'RSA/None/OAEPWithSHA256AndMGF1Padding',
|
||||||
|
'RSA'
|
||||||
|
]
|
||||||
|
|
||||||
|
export class NativeRsaBundle {
|
||||||
|
public publicKey: string
|
||||||
|
public privateKey: string
|
||||||
|
|
||||||
|
constructor(publicKey: string = '', privateKey: string = '') {
|
||||||
|
this.publicKey = publicKey
|
||||||
|
this.privateKey = privateKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NativeHarmonyRsaCore {
|
||||||
|
static createRsaBundle(keySize: number): NativeRsaBundle {
|
||||||
|
const generator = NativeHarmonyRsaCore.createKeyGenerator(keySize)
|
||||||
|
const keyPair = generator.generateKeyPairSync()
|
||||||
|
const publicKey = NativeHarmonyRsaCore.exportPublicPem(keyPair.pubKey)
|
||||||
|
const privateKey = NativeHarmonyRsaCore.exportPrivatePem(keyPair.priKey)
|
||||||
|
return new NativeRsaBundle(publicKey, privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
static rsaEncrypt(publicKey: string, plainText: string, outputKind: string | null): string {
|
||||||
|
const keyPair = NativeHarmonyRsaCore.importPemKeyPair(publicKey, null)
|
||||||
|
const cipher = NativeHarmonyRsaCore.createCipher()
|
||||||
|
cipher.initSync(cryptoFramework.CryptoMode.ENCRYPT_MODE, keyPair.pubKey, null)
|
||||||
|
const output = cipher.doFinalSync({ data: NativeHarmonyRsaCore.encodeUtf8(plainText) })
|
||||||
|
return NativeHarmonyRsaCore.encodeCipherOutput(output.data, outputKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
static rsaDecrypt(privateKey: string, cipherText: string, outputKind: string | null): string {
|
||||||
|
const keyPair = NativeHarmonyRsaCore.importPemKeyPair(null, privateKey)
|
||||||
|
const cipher = NativeHarmonyRsaCore.createCipher()
|
||||||
|
cipher.initSync(cryptoFramework.CryptoMode.DECRYPT_MODE, keyPair.priKey, null)
|
||||||
|
const sourceBytes = NativeHarmonyRsaCore.decodeCipherInput(cipherText)
|
||||||
|
const output = cipher.doFinalSync({ data: sourceBytes })
|
||||||
|
return NativeHarmonyRsaCore.decodePlainOutput(output.data, outputKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static createKeyGenerator(keySize: number): cryptoFramework.AsyKeyGenerator {
|
||||||
|
const candidates: string[] = [
|
||||||
|
'RSA' + keySize.toString(),
|
||||||
|
'RSA|' + keySize.toString(),
|
||||||
|
'RSA/' + keySize.toString(),
|
||||||
|
'RSA'
|
||||||
|
]
|
||||||
|
let lastError: Error | null = null
|
||||||
|
for (const algName of candidates) {
|
||||||
|
try {
|
||||||
|
return cryptoFramework.createAsyKeyGenerator(algName)
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastError ?? new Error('create RSA key generator failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
private static importPemKeyPair(publicKey: string | null, privateKey: string | null): cryptoFramework.KeyPair {
|
||||||
|
let lastError: Error | null = null
|
||||||
|
for (const algName of RSA_GENERATOR_ALGS) {
|
||||||
|
try {
|
||||||
|
const generator = cryptoFramework.createAsyKeyGenerator(algName)
|
||||||
|
return generator.convertPemKeySync(publicKey, privateKey)
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastError ?? new Error('import RSA PEM failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
private static createCipher(): cryptoFramework.Cipher {
|
||||||
|
let lastError: Error | null = null
|
||||||
|
for (const algName of RSA_CIPHER_ALGS) {
|
||||||
|
try {
|
||||||
|
return cryptoFramework.createCipher(algName)
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastError ?? new Error('create RSA cipher failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
private static exportPublicPem(publicKey: cryptoFramework.PubKey): string {
|
||||||
|
let lastError: Error | null = null
|
||||||
|
for (const formatName of PUBLIC_PEM_FORMATS) {
|
||||||
|
try {
|
||||||
|
return publicKey.getEncodedPem(formatName)
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastError ?? new Error('export public PEM failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
private static exportPrivatePem(privateKey: cryptoFramework.PriKey): string {
|
||||||
|
let lastError: Error | null = null
|
||||||
|
for (const formatName of PRIVATE_PEM_FORMATS) {
|
||||||
|
try {
|
||||||
|
return privateKey.getEncodedPem(formatName)
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastError ?? new Error('export private PEM failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
private static encodeUtf8(input: string): Uint8Array {
|
||||||
|
return UTF8_ENCODER.encodeInto(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decodeUtf8(input: Uint8Array): string {
|
||||||
|
return UTF8_DECODER.decode(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static encodeCipherOutput(input: Uint8Array, outputKind: string | null): string {
|
||||||
|
if (outputKind === 'hex') {
|
||||||
|
return NativeHarmonyRsaCore.bytesToHex(input)
|
||||||
|
}
|
||||||
|
return BASE64_HELPER.encodeToStringSync(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decodePlainOutput(input: Uint8Array, outputKind: string | null): string {
|
||||||
|
if (outputKind === 'hex') {
|
||||||
|
return NativeHarmonyRsaCore.bytesToHex(input)
|
||||||
|
}
|
||||||
|
return NativeHarmonyRsaCore.decodeUtf8(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decodeCipherInput(cipherText: string): Uint8Array {
|
||||||
|
const normalized = cipherText.trim()
|
||||||
|
if (NativeHarmonyRsaCore.looksLikeHex(normalized)) {
|
||||||
|
return NativeHarmonyRsaCore.hexToBytes(normalized)
|
||||||
|
}
|
||||||
|
return BASE64_HELPER.decodeSync(normalized)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static looksLikeHex(input: string): boolean {
|
||||||
|
return input.length > 0 && input.length % 2 == 0 && /^[0-9a-fA-F]+$/.test(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bytesToHex(input: Uint8Array): string {
|
||||||
|
let output = ''
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
const item = input[i]
|
||||||
|
if (item < 16) {
|
||||||
|
output += '0'
|
||||||
|
}
|
||||||
|
output += item.toString(16)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
private static hexToBytes(input: string): Uint8Array {
|
||||||
|
const length = input.length / 2
|
||||||
|
const output = new Uint8Array(length)
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const offset = i * 2
|
||||||
|
output[i] = Number.parseInt(input.substring(offset, offset + 2), 16)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,251 @@
|
|||||||
|
import type { CryptoByteCipherOptions, CryptoCodecKind, CryptoDesOptions, CryptoDigestKind, CryptoMacKind, CryptoRsaPair, CryptoRuntimeSnapshot, CryptoTextCipherOptions, CryptoTripleDesOptions, RsaOutputKind } from '../interface.uts'
|
||||||
|
import { buildRuntimeSnapshot, createDefaultByteCipherOptions, createDefaultDesOptions, createDefaultTextCipherOptions, createDefaultTripleDesOptions, normalizeByteCipherOptions, normalizeDesOptions, normalizeTextCipherOptions, normalizeTripleDesOptions, resolveHexOutput } from '../shared/options.uts'
|
||||||
|
import type { CompatBridge, CompatCrypto } from '../shared/compat.uts'
|
||||||
|
import { aesDecryptBytesCompat, aesDecryptCompat, aesEncryptBytesCompat, aesEncryptCompat, base64DecodeCompat, base64EncodeCompat, createCompatBridge, createCompatCrypto, desDecryptCompat, desEncryptCompat, generateRsaKeyPairCompat, hmacSha1Compat, hmacSha256Compat, hmacSha512Compat, md5Compat, rc4DecryptCompat, rc4EncryptCompat, rsaDecryptCompat, rsaEncryptCompat, sha1Compat, sha256Compat, sha512Compat } from '../shared/compat.uts'
|
||||||
|
|
||||||
|
function toNumberArray(source : Uint8Array) : number[] {
|
||||||
|
const output = [] as number[]
|
||||||
|
let index = 0
|
||||||
|
while (index < source.length) {
|
||||||
|
output.push(source[index])
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromNumberArray(source : number[]) : Uint8Array {
|
||||||
|
const output = new Uint8Array(source.length)
|
||||||
|
let index = 0
|
||||||
|
while (index < source.length) {
|
||||||
|
output[index] = source[index] & 0xff
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapString(value : string | null, message : string) : string {
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapNumberArray(value : number[] | null, message : string) : number[] {
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapBundle(value : NativeRsaBundle | null, message : string) : NativeRsaBundle {
|
||||||
|
if (value == null) {
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultTextCipherOptions() : CryptoTextCipherOptions {
|
||||||
|
return createDefaultTextCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultByteCipherOptions() : CryptoByteCipherOptions {
|
||||||
|
return createDefaultByteCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultTripleDesOptions() : CryptoTripleDesOptions {
|
||||||
|
return createDefaultTripleDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultDesOptions() : CryptoDesOptions {
|
||||||
|
return createDefaultDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function describeCryptoRuntime() : CryptoRuntimeSnapshot {
|
||||||
|
return buildRuntimeSnapshot('iOS', 'CommonCrypto', true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeText(codec : CryptoCodecKind, plainText : string) : string {
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.encodeCodec(codec, plainText), '?'), '编码失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeText(codec : CryptoCodecKind, encodedText : string) : string {
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.decodeCodec(codec, encodedText), '?'), '解码失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function digestText(kind : CryptoDigestKind, plainText : string) : string {
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.digestHex(kind, plainText), '?'), '摘要失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signText(kind : CryptoMacKind, secretText : string, plainText : string) : string {
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.hmacHex(kind, secretText, plainText), '?'), 'HMAC失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptText(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.aesEncryptText(secretText, plainText, actual.mode, actual.padding, actual.ivText, actual.keyLength), '?'), 'AES加密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptText(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.aesDecryptText(secretText, cipherText, actual.mode, actual.padding, actual.ivText, actual.keyLength), '?'), 'AES解密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptBytes(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
const response = unwrapNumberArray(UTSiOS.try(NativeCipherCore.aesEncryptBytes(toNumberArray(secretBytes), toNumberArray(plainBytes), actual.mode, actual.padding, actual.ivBytes == null ? null : toNumberArray(actual.ivBytes), actual.keyLength), '?'), 'AES字节加密失败')
|
||||||
|
return fromNumberArray(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptBytes(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
const response = unwrapNumberArray(UTSiOS.try(NativeCipherCore.aesDecryptBytes(toNumberArray(secretBytes), toNumberArray(cipherBytes), actual.mode, actual.padding, actual.ivBytes == null ? null : toNumberArray(actual.ivBytes), actual.keyLength), '?'), 'AES字节解密失败')
|
||||||
|
return fromNumberArray(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesEncryptText(secretText : string, plainText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.tripleDesEncryptText(secretText, plainText, actual.mode, actual.ivText), '?'), '3DES加密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesDecryptText(secretText : string, cipherText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.tripleDesDecryptText(secretText, cipherText, actual.mode, actual.ivText), '?'), '3DES解密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncryptText(secretText : string, plainText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.desEncryptText(secretText, plainText, actual.mode, actual.padding, actual.ivText), '?'), 'DES加密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecryptText(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.desDecryptText(secretText, cipherText, actual.mode, actual.padding, actual.ivText), '?'), 'DES解密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4EncryptText(secretText : string, plainText : string) : string {
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.rc4EncryptToHex(secretText, plainText), '?'), 'RC4加密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4DecryptText(secretText : string, cipherHexText : string) : string {
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.rc4DecryptFromHex(secretText, cipherHexText), '?'), 'RC4解密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRsaPair(keySize : number) : CryptoRsaPair {
|
||||||
|
const bundle = unwrapBundle(UTSiOS.try(NativeCipherCore.createRsaBundle(keySize), '?'), 'RSA密钥生成失败')
|
||||||
|
return {
|
||||||
|
publicKey: bundle.publicKey,
|
||||||
|
privateKey: bundle.privateKey
|
||||||
|
} as CryptoRsaPair
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncryptText(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.rsaEncrypt(publicKey, plainText, resolveHexOutput(outputKind) ? 'hex' : 'base64'), '?'), 'RSA加密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecryptText(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return unwrapString(UTSiOS.try(NativeCipherCore.rsaDecrypt(privateKey, cipherText, resolveHexOutput(outputKind) ? 'hex' : 'base64'), '?'), 'RSA解密失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
function compatBridge() : CompatBridge {
|
||||||
|
return createCompatBridge(
|
||||||
|
(codec : CryptoCodecKind, plainText : string) : string => encodeText(codec, plainText),
|
||||||
|
(codec : CryptoCodecKind, encodedText : string) : string => decodeText(codec, encodedText),
|
||||||
|
(kind : CryptoDigestKind, plainText : string) : string => digestText(kind, plainText),
|
||||||
|
(kind : CryptoMacKind, secretText : string, plainText : string) : string => signText(kind, secretText, plainText),
|
||||||
|
(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string => aesEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string => aesDecryptText(secretText, cipherText, options),
|
||||||
|
(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesEncryptBytes(secretBytes, plainBytes, options),
|
||||||
|
(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesDecryptBytes(secretBytes, cipherBytes, options),
|
||||||
|
(secretText : string, plainText : string, options : CryptoDesOptions | null) : string => desEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string => desDecryptText(secretText, cipherText, options),
|
||||||
|
(keySize : number) : CryptoRsaPair => createRsaPair(keySize),
|
||||||
|
(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string => rsaEncryptText(publicKey, plainText, outputKind),
|
||||||
|
(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string => rsaDecryptText(privateKey, cipherText, outputKind),
|
||||||
|
(secretText : string, plainText : string) : string => rc4EncryptText(secretText, plainText),
|
||||||
|
(secretText : string, cipherHexText : string) : string => rc4DecryptText(secretText, cipherHexText)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Encode(input : string) : string {
|
||||||
|
return base64EncodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Decode(input : string) : string {
|
||||||
|
return base64DecodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function md5(input : string) : string {
|
||||||
|
return md5Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha1(input : string) : string {
|
||||||
|
return sha1Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha256(input : string) : string {
|
||||||
|
return sha256Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha512(input : string) : string {
|
||||||
|
return sha512Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha1(key : string, data : string) : string {
|
||||||
|
return hmacSha1Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha256(key : string, data : string) : string {
|
||||||
|
return hmacSha256Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha512(key : string, data : string) : string {
|
||||||
|
return hmacSha512Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesEncryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesDecryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesEncryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesDecryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desEncryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desDecryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateRSAKeyPair(keySize : number = 2048) : CryptoRsaPair {
|
||||||
|
return generateRsaKeyPairCompat(compatBridge(), keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncrypt(publicKey : string, data : string) : string {
|
||||||
|
return rsaEncryptCompat(compatBridge(), publicKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecrypt(privateKey : string, data : string) : string {
|
||||||
|
return rsaDecryptCompat(compatBridge(), privateKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Encrypt(key : string, data : string) : string {
|
||||||
|
return rc4EncryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Decrypt(key : string, data : string) : string {
|
||||||
|
return rc4DecryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCrypto() : CompatCrypto {
|
||||||
|
return createCompatCrypto(compatBridge())
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "laoqianjunzi-crypto-runtime-deps",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "laoqianjunzi-crypto-runtime-deps",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"crypto-es": "^2.1.0",
|
||||||
|
"jsencrypt": "^3.5.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/crypto-es": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/crypto-es/-/crypto-es-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-C5Dbuv4QTPGuloy5c5Vv/FZHtmK+lobLAypFfuRaBbwCsk3qbCWWESCH3MUcBsrgXloRNMrzwUAiPg4U6+IaKA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/jsencrypt": {
|
||||||
|
"version": "3.5.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.5.4.tgz",
|
||||||
|
"integrity": "sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "laoqianjunzi-crypto-runtime-deps",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"crypto-es": "^2.1.0",
|
||||||
|
"jsencrypt": "^3.5.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export * from './interface.uts'
|
||||||
|
export * from './unierror.uts'
|
||||||
|
|
||||||
|
// #ifdef APP-ANDROID
|
||||||
|
export * from './app-android/index.uts'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef APP-IOS
|
||||||
|
export * from './app-ios/index.uts'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef APP-HARMONY
|
||||||
|
export * from './app-harmony/index.uts'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef WEB
|
||||||
|
export * from './web/index.uts'
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
export * from './mp-weixin/index.uts'
|
||||||
|
// #endif
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
export type CryptoDigestKind = "md5" | "sha1" | "sha256" | "sha512"
|
||||||
|
|
||||||
|
export type CryptoMacKind = "sha1" | "sha256" | "sha512"
|
||||||
|
|
||||||
|
export type CryptoCodecKind = "utf8" | "hex" | "base64" | "base64url" | "latin1" | "utf16"
|
||||||
|
|
||||||
|
export type CryptoBlockMode = "ECB" | "CBC" | "CFB" | "CTR" | "CTRGladman" | "OFB"
|
||||||
|
|
||||||
|
export type CryptoPaddingKind = "PKCS7" | "ANSI_X923" | "ISO_10126" | "ISO_97971" | "NONE" | "ZERO"
|
||||||
|
|
||||||
|
export type RsaOutputKind = "base64" | "hex"
|
||||||
|
|
||||||
|
export type CryptoTextCipherOptions = {
|
||||||
|
mode : CryptoBlockMode,
|
||||||
|
padding : CryptoPaddingKind,
|
||||||
|
ivText : string | null,
|
||||||
|
keyLength : number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CryptoByteCipherOptions = {
|
||||||
|
mode : CryptoBlockMode,
|
||||||
|
padding : CryptoPaddingKind,
|
||||||
|
ivBytes : Uint8Array | null,
|
||||||
|
keyLength : number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CryptoTripleDesOptions = {
|
||||||
|
mode : "ECB" | "CBC",
|
||||||
|
ivText : string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CryptoDesOptions = {
|
||||||
|
mode : CryptoBlockMode,
|
||||||
|
padding : CryptoPaddingKind,
|
||||||
|
ivText : string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CryptoRsaPair = {
|
||||||
|
publicKey : string,
|
||||||
|
privateKey : string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RSARANDOMKEY = CryptoRsaPair
|
||||||
|
|
||||||
|
export type RSAKeyPair = CryptoRsaPair
|
||||||
|
|
||||||
|
export type CryptoRuntimeSnapshot = {
|
||||||
|
platformName : string,
|
||||||
|
engineName : string,
|
||||||
|
supportsAdvancedModes : boolean,
|
||||||
|
usesNativeBridge : boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CryptoBridgeErrorCode = 7401 | 7402 | 7403 | 7404 | 7405 | 7406 | 7407 | 7408 | 7409 | 7410
|
||||||
|
|
||||||
|
export interface CryptoBridgeFailure extends IUniError {
|
||||||
|
errCode : CryptoBridgeErrorCode
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import type { CryptoByteCipherOptions, CryptoCodecKind, CryptoDesOptions, CryptoDigestKind, CryptoMacKind, CryptoRsaPair, CryptoRuntimeSnapshot, CryptoTextCipherOptions, CryptoTripleDesOptions, RsaOutputKind } from '../interface.uts'
|
||||||
|
import { buildRuntimeSnapshot, createDefaultByteCipherOptions, createDefaultDesOptions, createDefaultTextCipherOptions, createDefaultTripleDesOptions, normalizeByteCipherOptions, normalizeDesOptions, normalizeTextCipherOptions, normalizeTripleDesOptions, resolveHexOutput } from '../shared/options.uts'
|
||||||
|
import { aesDecryptBytesPortable, aesDecryptTextPortable, aesEncryptBytesPortable, aesEncryptTextPortable, createRsaPairPortable, decodeTextPortable, desDecryptTextPortable, desEncryptTextPortable, digestTextPortable, encodeTextPortable, rc4DecryptTextPortable, rc4EncryptTextPortable, rsaDecryptTextPortable, rsaEncryptTextPortable, signTextPortable, tripleDesDecryptTextPortable, tripleDesEncryptTextPortable } from '../shared/portable_crypto'
|
||||||
|
import type { CompatBridge, CompatCrypto } from '../shared/compat.uts'
|
||||||
|
import { aesDecryptBytesCompat, aesDecryptCompat, aesEncryptBytesCompat, aesEncryptCompat, base64DecodeCompat, base64EncodeCompat, createCompatBridge, createCompatCrypto, desDecryptCompat, desEncryptCompat, generateRsaKeyPairCompat, hmacSha1Compat, hmacSha256Compat, hmacSha512Compat, md5Compat, rc4DecryptCompat, rc4EncryptCompat, rsaDecryptCompat, rsaEncryptCompat, sha1Compat, sha256Compat, sha512Compat } from '../shared/compat.uts'
|
||||||
|
|
||||||
|
export function defaultTextCipherOptions() : CryptoTextCipherOptions {
|
||||||
|
return createDefaultTextCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultByteCipherOptions() : CryptoByteCipherOptions {
|
||||||
|
return createDefaultByteCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultTripleDesOptions() : CryptoTripleDesOptions {
|
||||||
|
return createDefaultTripleDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultDesOptions() : CryptoDesOptions {
|
||||||
|
return createDefaultDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function describeCryptoRuntime() : CryptoRuntimeSnapshot {
|
||||||
|
return buildRuntimeSnapshot('mp-weixin', 'crypto-es', true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeText(codec : CryptoCodecKind, plainText : string) : string {
|
||||||
|
return encodeTextPortable(codec, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeText(codec : CryptoCodecKind, encodedText : string) : string {
|
||||||
|
return decodeTextPortable(codec, encodedText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function digestText(kind : CryptoDigestKind, plainText : string) : string {
|
||||||
|
return digestTextPortable(kind, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signText(kind : CryptoMacKind, secretText : string, plainText : string) : string {
|
||||||
|
return signTextPortable(kind, secretText, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptText(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
return aesEncryptTextPortable(secretText, plainText, actual.mode, actual.padding, actual.ivText, actual.keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptText(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
return aesDecryptTextPortable(secretText, cipherText, actual.mode, actual.padding, actual.ivText, actual.keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptBytes(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
return aesEncryptBytesPortable(secretBytes, plainBytes, actual.mode, actual.padding, actual.ivBytes, actual.keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptBytes(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
return aesDecryptBytesPortable(secretBytes, cipherBytes, actual.mode, actual.padding, actual.ivBytes, actual.keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesEncryptText(secretText : string, plainText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
return tripleDesEncryptTextPortable(secretText, plainText, actual.mode, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesDecryptText(secretText : string, cipherText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
return tripleDesDecryptTextPortable(secretText, cipherText, actual.mode, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncryptText(secretText : string, plainText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
return desEncryptTextPortable(secretText, plainText, actual.mode, actual.padding, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecryptText(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
return desDecryptTextPortable(secretText, cipherText, actual.mode, actual.padding, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4EncryptText(secretText : string, plainText : string) : string {
|
||||||
|
return rc4EncryptTextPortable(secretText, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4DecryptText(secretText : string, cipherHexText : string) : string {
|
||||||
|
return rc4DecryptTextPortable(secretText, cipherHexText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRsaPair(keySize : number) : CryptoRsaPair {
|
||||||
|
const bundle = createRsaPairPortable(keySize)
|
||||||
|
return {
|
||||||
|
publicKey: bundle.publicKey,
|
||||||
|
privateKey: bundle.privateKey
|
||||||
|
} as CryptoRsaPair
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncryptText(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return rsaEncryptTextPortable(publicKey, plainText, resolveHexOutput(outputKind) ? 'hex' : 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecryptText(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return rsaDecryptTextPortable(privateKey, cipherText, resolveHexOutput(outputKind) ? 'hex' : 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
function compatBridge() : CompatBridge {
|
||||||
|
return createCompatBridge(
|
||||||
|
(codec : CryptoCodecKind, plainText : string) : string => encodeText(codec, plainText),
|
||||||
|
(codec : CryptoCodecKind, encodedText : string) : string => decodeText(codec, encodedText),
|
||||||
|
(kind : CryptoDigestKind, plainText : string) : string => digestText(kind, plainText),
|
||||||
|
(kind : CryptoMacKind, secretText : string, plainText : string) : string => signText(kind, secretText, plainText),
|
||||||
|
(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string => aesEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string => aesDecryptText(secretText, cipherText, options),
|
||||||
|
(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesEncryptBytes(secretBytes, plainBytes, options),
|
||||||
|
(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesDecryptBytes(secretBytes, cipherBytes, options),
|
||||||
|
(secretText : string, plainText : string, options : CryptoDesOptions | null) : string => desEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string => desDecryptText(secretText, cipherText, options),
|
||||||
|
(keySize : number) : CryptoRsaPair => createRsaPair(keySize),
|
||||||
|
(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string => rsaEncryptText(publicKey, plainText, outputKind),
|
||||||
|
(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string => rsaDecryptText(privateKey, cipherText, outputKind),
|
||||||
|
(secretText : string, plainText : string) : string => rc4EncryptText(secretText, plainText),
|
||||||
|
(secretText : string, cipherHexText : string) : string => rc4DecryptText(secretText, cipherHexText)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Encode(input : string) : string {
|
||||||
|
return base64EncodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Decode(input : string) : string {
|
||||||
|
return base64DecodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function md5(input : string) : string {
|
||||||
|
return md5Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha1(input : string) : string {
|
||||||
|
return sha1Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha256(input : string) : string {
|
||||||
|
return sha256Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha512(input : string) : string {
|
||||||
|
return sha512Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha1(key : string, data : string) : string {
|
||||||
|
return hmacSha1Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha256(key : string, data : string) : string {
|
||||||
|
return hmacSha256Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha512(key : string, data : string) : string {
|
||||||
|
return hmacSha512Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesEncryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesDecryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesEncryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesDecryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desEncryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desDecryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateRSAKeyPair(keySize : number = 2048) : CryptoRsaPair {
|
||||||
|
return generateRsaKeyPairCompat(compatBridge(), keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncrypt(publicKey : string, data : string) : string {
|
||||||
|
return rsaEncryptCompat(compatBridge(), publicKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecrypt(privateKey : string, data : string) : string {
|
||||||
|
return rsaDecryptCompat(compatBridge(), privateKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Encrypt(key : string, data : string) : string {
|
||||||
|
return rc4EncryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Decrypt(key : string, data : string) : string {
|
||||||
|
return rc4DecryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCrypto() : CompatCrypto {
|
||||||
|
return createCompatCrypto(compatBridge())
|
||||||
|
}
|
||||||
@@ -0,0 +1,771 @@
|
|||||||
|
import type { CryptoBlockMode, CryptoByteCipherOptions, CryptoCodecKind, CryptoDesOptions, CryptoDigestKind, CryptoMacKind, CryptoPaddingKind, CryptoRsaPair, CryptoTextCipherOptions, RsaOutputKind } from '../interface.uts'
|
||||||
|
|
||||||
|
const LIME_MODE_CBC = 1
|
||||||
|
const LIME_MODE_CFB = 2
|
||||||
|
const LIME_MODE_CTR = 3
|
||||||
|
const LIME_MODE_CTR_GLADMAN = 4
|
||||||
|
const LIME_MODE_ECB = 5
|
||||||
|
const LIME_MODE_OFB = 6
|
||||||
|
|
||||||
|
const LIME_PAD_PKCS7 = 1
|
||||||
|
const LIME_PAD_ANSI_X923 = 2
|
||||||
|
const LIME_PAD_ISO_10126 = 3
|
||||||
|
const LIME_PAD_ISO_97971 = 4
|
||||||
|
const LIME_PAD_NONE = 5
|
||||||
|
const LIME_PAD_ZERO = 6
|
||||||
|
|
||||||
|
const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||||
|
|
||||||
|
export type CompatBridge = {
|
||||||
|
encodeText : (codec : CryptoCodecKind, plainText : string) => string,
|
||||||
|
decodeText : (codec : CryptoCodecKind, encodedText : string) => string,
|
||||||
|
digestText : (kind : CryptoDigestKind, plainText : string) => string,
|
||||||
|
signText : (kind : CryptoMacKind, secretText : string, plainText : string) => string,
|
||||||
|
aesEncryptText : (secretText : string, plainText : string, options : CryptoTextCipherOptions | null) => string,
|
||||||
|
aesDecryptText : (secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) => string,
|
||||||
|
aesEncryptBytes : (secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) => Uint8Array,
|
||||||
|
aesDecryptBytes : (secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) => Uint8Array,
|
||||||
|
desEncryptText : (secretText : string, plainText : string, options : CryptoDesOptions | null) => string,
|
||||||
|
desDecryptText : (secretText : string, cipherText : string, options : CryptoDesOptions | null) => string,
|
||||||
|
createRsaPair : (keySize : number) => CryptoRsaPair,
|
||||||
|
rsaEncryptText : (publicKey : string, plainText : string, outputKind : RsaOutputKind | null) => string,
|
||||||
|
rsaDecryptText : (privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) => string,
|
||||||
|
rc4EncryptText : (secretText : string, plainText : string) => string,
|
||||||
|
rc4DecryptText : (secretText : string, cipherHexText : string) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompatEncSet = {
|
||||||
|
Utf8 : CompatEncoder,
|
||||||
|
Hex : CompatEncoder,
|
||||||
|
Base64 : CompatEncoder,
|
||||||
|
Latin1 : CompatEncoder,
|
||||||
|
Utf16 : CompatEncoder,
|
||||||
|
Base64url : CompatEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompatModeSet = {
|
||||||
|
CBC : number,
|
||||||
|
CFB : number,
|
||||||
|
CTR : number,
|
||||||
|
CTRGladman : number,
|
||||||
|
ECB : number,
|
||||||
|
OFB : number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompatPadSet = {
|
||||||
|
Pkcs7 : number,
|
||||||
|
AnsiX923 : number,
|
||||||
|
Iso10126 : number,
|
||||||
|
Iso97971 : number,
|
||||||
|
NoPadding : number,
|
||||||
|
ZeroPadding : number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCompatBridge(
|
||||||
|
encodeText : (codec : CryptoCodecKind, plainText : string) => string,
|
||||||
|
decodeText : (codec : CryptoCodecKind, encodedText : string) => string,
|
||||||
|
digestText : (kind : CryptoDigestKind, plainText : string) => string,
|
||||||
|
signText : (kind : CryptoMacKind, secretText : string, plainText : string) => string,
|
||||||
|
aesEncryptText : (secretText : string, plainText : string, options : CryptoTextCipherOptions | null) => string,
|
||||||
|
aesDecryptText : (secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) => string,
|
||||||
|
aesEncryptBytes : (secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) => Uint8Array,
|
||||||
|
aesDecryptBytes : (secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) => Uint8Array,
|
||||||
|
desEncryptText : (secretText : string, plainText : string, options : CryptoDesOptions | null) => string,
|
||||||
|
desDecryptText : (secretText : string, cipherText : string, options : CryptoDesOptions | null) => string,
|
||||||
|
createRsaPair : (keySize : number) => CryptoRsaPair,
|
||||||
|
rsaEncryptText : (publicKey : string, plainText : string, outputKind : RsaOutputKind | null) => string,
|
||||||
|
rsaDecryptText : (privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) => string,
|
||||||
|
rc4EncryptText : (secretText : string, plainText : string) => string,
|
||||||
|
rc4DecryptText : (secretText : string, cipherHexText : string) => string
|
||||||
|
) : CompatBridge {
|
||||||
|
return {
|
||||||
|
encodeText,
|
||||||
|
decodeText,
|
||||||
|
digestText,
|
||||||
|
signText,
|
||||||
|
aesEncryptText,
|
||||||
|
aesDecryptText,
|
||||||
|
aesEncryptBytes,
|
||||||
|
aesDecryptBytes,
|
||||||
|
desEncryptText,
|
||||||
|
desDecryptText,
|
||||||
|
createRsaPair,
|
||||||
|
rsaEncryptText,
|
||||||
|
rsaDecryptText,
|
||||||
|
rc4EncryptText,
|
||||||
|
rc4DecryptText
|
||||||
|
} as CompatBridge
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneBytes(input : Uint8Array) : Uint8Array {
|
||||||
|
const output = new Uint8Array(input.length)
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
output[index] = input[index]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToWords(input : Uint8Array) : number[] {
|
||||||
|
const output = [] as number[]
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
let word = 0
|
||||||
|
let offset = 0
|
||||||
|
while (offset < 4 && index + offset < input.length) {
|
||||||
|
word |= input[index + offset] << (24 - offset * 8)
|
||||||
|
offset += 1
|
||||||
|
}
|
||||||
|
output.push(word)
|
||||||
|
index += 4
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function byteToHex(value : number) : string {
|
||||||
|
let part = value.toString(16)
|
||||||
|
if (part.length < 2) {
|
||||||
|
part = '0' + part
|
||||||
|
}
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToHex(input : Uint8Array) : string {
|
||||||
|
let output = ''
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
output += byteToHex(input[index] & 0xff)
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexValueOfChar(ch : string) : number {
|
||||||
|
const lower = ch.toLowerCase()
|
||||||
|
const code = lower.charCodeAt(0)
|
||||||
|
const actualCode = code == null ? 0 : code
|
||||||
|
if (lower >= '0' && lower <= '9') {
|
||||||
|
return actualCode - 48
|
||||||
|
}
|
||||||
|
return actualCode - 87
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToBytes(hexText : string) : Uint8Array {
|
||||||
|
const actualLength = hexText.length % 2 == 0 ? hexText.length : hexText.length - 1
|
||||||
|
const output = new Uint8Array(actualLength / 2)
|
||||||
|
let index = 0
|
||||||
|
while (index < actualLength) {
|
||||||
|
const high = hexValueOfChar(hexText.substring(index, index + 1))
|
||||||
|
const low = hexValueOfChar(hexText.substring(index + 1, index + 2))
|
||||||
|
const targetIndex = index / 2
|
||||||
|
output[targetIndex] = ((high << 4) | low) & 0xff
|
||||||
|
index += 2
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64IndexOf(ch : string) : number {
|
||||||
|
return BASE64_ALPHABET.indexOf(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBase64UrlText(input : string) : string {
|
||||||
|
let output = ''
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
const ch = input.substring(index, index + 1)
|
||||||
|
if (ch == '+') {
|
||||||
|
output += '-'
|
||||||
|
} else if (ch == '/') {
|
||||||
|
output += '_'
|
||||||
|
} else if (ch != '=') {
|
||||||
|
output += ch
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromBase64UrlText(input : string) : string {
|
||||||
|
let output = ''
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
const ch = input.substring(index, index + 1)
|
||||||
|
if (ch == '-') {
|
||||||
|
output += '+'
|
||||||
|
} else if (ch == '_') {
|
||||||
|
output += '/'
|
||||||
|
} else {
|
||||||
|
output += ch
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
while (output.length % 4 != 0) {
|
||||||
|
output += '='
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToBase64(input : Uint8Array, urlSafe : boolean) : string {
|
||||||
|
let output = ''
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
const first = input[index] & 0xff
|
||||||
|
const hasSecond = index + 1 < input.length
|
||||||
|
const hasThird = index + 2 < input.length
|
||||||
|
const second = hasSecond ? input[index + 1] & 0xff : 0
|
||||||
|
const third = hasThird ? input[index + 2] & 0xff : 0
|
||||||
|
const block = (first << 16) | (second << 8) | third
|
||||||
|
output += BASE64_ALPHABET.substring((block >>> 18) & 0x3f, ((block >>> 18) & 0x3f) + 1)
|
||||||
|
output += BASE64_ALPHABET.substring((block >>> 12) & 0x3f, ((block >>> 12) & 0x3f) + 1)
|
||||||
|
if (hasSecond) {
|
||||||
|
output += BASE64_ALPHABET.substring((block >>> 6) & 0x3f, ((block >>> 6) & 0x3f) + 1)
|
||||||
|
} else {
|
||||||
|
output += '='
|
||||||
|
}
|
||||||
|
if (hasThird) {
|
||||||
|
output += BASE64_ALPHABET.substring(block & 0x3f, (block & 0x3f) + 1)
|
||||||
|
} else {
|
||||||
|
output += '='
|
||||||
|
}
|
||||||
|
index += 3
|
||||||
|
}
|
||||||
|
if (urlSafe) {
|
||||||
|
return toBase64UrlText(output)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBase64Text(input : string) : string {
|
||||||
|
return fromBase64UrlText(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64ToBytes(input : string, urlSafe : boolean) : Uint8Array {
|
||||||
|
const source = urlSafe ? normalizeBase64Text(input) : input
|
||||||
|
const output = [] as number[]
|
||||||
|
let index = 0
|
||||||
|
while (index < source.length) {
|
||||||
|
const char1 = source.substring(index, index + 1)
|
||||||
|
const char2 = source.substring(index + 1, index + 2)
|
||||||
|
const char3 = source.substring(index + 2, index + 3)
|
||||||
|
const char4 = source.substring(index + 3, index + 4)
|
||||||
|
const value1 = base64IndexOf(char1)
|
||||||
|
const value2 = base64IndexOf(char2)
|
||||||
|
const value3 = char3 == '=' ? -1 : base64IndexOf(char3)
|
||||||
|
const value4 = char4 == '=' ? -1 : base64IndexOf(char4)
|
||||||
|
const block = (value1 << 18) | (value2 << 12) | ((value3 < 0 ? 0 : value3) << 6) | (value4 < 0 ? 0 : value4)
|
||||||
|
output.push((block >>> 16) & 0xff)
|
||||||
|
if (value3 >= 0) {
|
||||||
|
output.push((block >>> 8) & 0xff)
|
||||||
|
}
|
||||||
|
if (value4 >= 0) {
|
||||||
|
output.push(block & 0xff)
|
||||||
|
}
|
||||||
|
index += 4
|
||||||
|
}
|
||||||
|
return new Uint8Array(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
function latin1ToBytes(input : string) : Uint8Array {
|
||||||
|
const output = new Uint8Array(input.length)
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
const code = input.charCodeAt(index)
|
||||||
|
output[index] = (code == null ? 0 : code) & 0xff
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToLatin1(input : Uint8Array) : string {
|
||||||
|
let output = ''
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
output += String.fromCharCode(input[index] & 0xff)
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function utf16ToBytes(input : string) : Uint8Array {
|
||||||
|
const output = new Uint8Array(input.length * 2)
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
const code = input.charCodeAt(index)
|
||||||
|
const actualCode = code == null ? 0 : code
|
||||||
|
output[index * 2] = (actualCode >>> 8) & 0xff
|
||||||
|
output[index * 2 + 1] = actualCode & 0xff
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToUtf16(input : Uint8Array) : string {
|
||||||
|
let output = ''
|
||||||
|
let index = 0
|
||||||
|
while (index + 1 < input.length) {
|
||||||
|
const code = ((input[index] & 0xff) << 8) | (input[index + 1] & 0xff)
|
||||||
|
output += String.fromCharCode(code)
|
||||||
|
index += 2
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function textToUtf8Bytes(input : string, bridge : CompatBridge) : Uint8Array {
|
||||||
|
return hexToBytes(bridge.encodeText('hex', input))
|
||||||
|
}
|
||||||
|
|
||||||
|
function utf8BytesToText(input : Uint8Array, bridge : CompatBridge) : string {
|
||||||
|
return bridge.decodeText('hex', bytesToHex(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCodecBytes(kind : CryptoCodecKind, input : string, bridge : CompatBridge) : Uint8Array {
|
||||||
|
if (kind == 'utf8') {
|
||||||
|
return textToUtf8Bytes(input, bridge)
|
||||||
|
}
|
||||||
|
if (kind == 'hex') {
|
||||||
|
return hexToBytes(input)
|
||||||
|
}
|
||||||
|
if (kind == 'base64') {
|
||||||
|
return base64ToBytes(input, false)
|
||||||
|
}
|
||||||
|
if (kind == 'base64url') {
|
||||||
|
return base64ToBytes(input, true)
|
||||||
|
}
|
||||||
|
if (kind == 'latin1') {
|
||||||
|
return latin1ToBytes(input)
|
||||||
|
}
|
||||||
|
return utf16ToBytes(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyCodecBytes(kind : CryptoCodecKind, input : Uint8Array, bridge : CompatBridge) : string {
|
||||||
|
if (kind == 'utf8') {
|
||||||
|
return utf8BytesToText(input, bridge)
|
||||||
|
}
|
||||||
|
if (kind == 'hex') {
|
||||||
|
return bytesToHex(input)
|
||||||
|
}
|
||||||
|
if (kind == 'base64') {
|
||||||
|
return bytesToBase64(input, false)
|
||||||
|
}
|
||||||
|
if (kind == 'base64url') {
|
||||||
|
return bytesToBase64(input, true)
|
||||||
|
}
|
||||||
|
if (kind == 'latin1') {
|
||||||
|
return bytesToLatin1(input)
|
||||||
|
}
|
||||||
|
return bytesToUtf16(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeKeyLength(keySize : number | null) : number | null {
|
||||||
|
if (keySize == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (keySize == 128) {
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
if (keySize == 192) {
|
||||||
|
return 24
|
||||||
|
}
|
||||||
|
if (keySize == 256) {
|
||||||
|
return 32
|
||||||
|
}
|
||||||
|
if (keySize == 16 || keySize == 24 || keySize == 32) {
|
||||||
|
return keySize
|
||||||
|
}
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
|
||||||
|
function toLimeMode(mode : number | null) : CryptoBlockMode {
|
||||||
|
if (mode == LIME_MODE_CFB) {
|
||||||
|
return 'CFB'
|
||||||
|
}
|
||||||
|
if (mode == LIME_MODE_CTR) {
|
||||||
|
return 'CTR'
|
||||||
|
}
|
||||||
|
if (mode == LIME_MODE_CTR_GLADMAN) {
|
||||||
|
return 'CTRGladman'
|
||||||
|
}
|
||||||
|
if (mode == LIME_MODE_ECB) {
|
||||||
|
return 'ECB'
|
||||||
|
}
|
||||||
|
if (mode == LIME_MODE_OFB) {
|
||||||
|
return 'OFB'
|
||||||
|
}
|
||||||
|
return 'CBC'
|
||||||
|
}
|
||||||
|
|
||||||
|
function toLimePadding(padding : number | null) : CryptoPaddingKind {
|
||||||
|
if (padding == LIME_PAD_ANSI_X923) {
|
||||||
|
return 'ANSI_X923'
|
||||||
|
}
|
||||||
|
if (padding == LIME_PAD_ISO_10126) {
|
||||||
|
return 'ISO_10126'
|
||||||
|
}
|
||||||
|
if (padding == LIME_PAD_ISO_97971) {
|
||||||
|
return 'ISO_97971'
|
||||||
|
}
|
||||||
|
if (padding == LIME_PAD_NONE) {
|
||||||
|
return 'NONE'
|
||||||
|
}
|
||||||
|
if (padding == LIME_PAD_ZERO) {
|
||||||
|
return 'ZERO'
|
||||||
|
}
|
||||||
|
return 'PKCS7'
|
||||||
|
}
|
||||||
|
|
||||||
|
function readConfigNumber(cfg : UTSJSONObject | null, key : string) : number | null {
|
||||||
|
if (cfg == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return cfg.getNumber(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
function readConfigWordArray(cfg : UTSJSONObject | null, key : string) : CompatWordArray | null {
|
||||||
|
if (cfg == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const raw = cfg.get(key) as object | null
|
||||||
|
if (raw != null && raw instanceof CompatWordArray) {
|
||||||
|
return raw as CompatWordArray
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLimeTextOptions(cfg : UTSJSONObject | null, bridge : CompatBridge) : CryptoTextCipherOptions {
|
||||||
|
const iv = readConfigWordArray(cfg, 'iv')
|
||||||
|
return {
|
||||||
|
mode: toLimeMode(readConfigNumber(cfg, 'mode')),
|
||||||
|
padding: toLimePadding(readConfigNumber(cfg, 'padding')),
|
||||||
|
ivText: iv == null ? null : utf8BytesToText(iv.toUint8Array(), bridge),
|
||||||
|
keyLength: null
|
||||||
|
} as CryptoTextCipherOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLimeDesOptions(cfg : UTSJSONObject | null, bridge : CompatBridge) : CryptoDesOptions {
|
||||||
|
const iv = readConfigWordArray(cfg, 'iv')
|
||||||
|
return {
|
||||||
|
mode: toLimeMode(readConfigNumber(cfg, 'mode')),
|
||||||
|
padding: toLimePadding(readConfigNumber(cfg, 'padding')),
|
||||||
|
ivText: iv == null ? null : utf8BytesToText(iv.toUint8Array(), bridge)
|
||||||
|
} as CryptoDesOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildXTextOptions(mode : string, iv : string | null, keySize : number | null) : CryptoTextCipherOptions {
|
||||||
|
return {
|
||||||
|
mode: mode == 'CBC' ? 'CBC' : 'ECB',
|
||||||
|
padding: 'PKCS7',
|
||||||
|
ivText: iv,
|
||||||
|
keyLength: normalizeKeyLength(keySize)
|
||||||
|
} as CryptoTextCipherOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildXByteOptions(mode : string, iv : Uint8Array | null, keySize : number | null) : CryptoByteCipherOptions {
|
||||||
|
return {
|
||||||
|
mode: mode == 'CBC' ? 'CBC' : 'ECB',
|
||||||
|
padding: 'PKCS7',
|
||||||
|
ivBytes: iv,
|
||||||
|
keyLength: normalizeKeyLength(keySize)
|
||||||
|
} as CryptoByteCipherOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildXDesOptions(mode : string, iv : string | null) : CryptoDesOptions {
|
||||||
|
return {
|
||||||
|
mode: mode == 'CBC' ? 'CBC' : 'ECB',
|
||||||
|
padding: 'PKCS7',
|
||||||
|
ivText: iv
|
||||||
|
} as CryptoDesOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyToText(key : string | CompatWordArray, bridge : CompatBridge) : string {
|
||||||
|
if (typeof key == 'string') {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
if (key instanceof CompatWordArray) {
|
||||||
|
return utf8BytesToText(key.toUint8Array(), bridge)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompatWordArray {
|
||||||
|
words : number[]
|
||||||
|
sigBytes : number
|
||||||
|
private rawBytes : Uint8Array
|
||||||
|
|
||||||
|
constructor(rawBytes : Uint8Array) {
|
||||||
|
this.rawBytes = cloneBytes(rawBytes)
|
||||||
|
this.words = bytesToWords(this.rawBytes)
|
||||||
|
this.sigBytes = this.rawBytes.length
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(encoder : CompatEncoder | null = null) : string {
|
||||||
|
if (encoder == null) {
|
||||||
|
return bytesToHex(this.rawBytes)
|
||||||
|
}
|
||||||
|
return encoder.stringify(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
toUint8Array() : Uint8Array {
|
||||||
|
return cloneBytes(this.rawBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompatCipherResult {
|
||||||
|
ciphertext : CompatWordArray
|
||||||
|
private serialized : string
|
||||||
|
|
||||||
|
constructor(serialized : string, cipherBytes : Uint8Array) {
|
||||||
|
this.serialized = serialized
|
||||||
|
this.ciphertext = new CompatWordArray(cipherBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(encoder : CompatEncoder | null = null) : string {
|
||||||
|
if (encoder == null) {
|
||||||
|
return this.serialized
|
||||||
|
}
|
||||||
|
return this.ciphertext.toString(encoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompatEncoder {
|
||||||
|
private kind : CryptoCodecKind
|
||||||
|
private bridge : CompatBridge
|
||||||
|
|
||||||
|
constructor(kind : CryptoCodecKind, bridge : CompatBridge) {
|
||||||
|
this.kind = kind
|
||||||
|
this.bridge = bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(input : string) : CompatWordArray {
|
||||||
|
return new CompatWordArray(parseCodecBytes(this.kind, input, this.bridge))
|
||||||
|
}
|
||||||
|
|
||||||
|
stringify(wordArray : CompatWordArray) : string {
|
||||||
|
return stringifyCodecBytes(this.kind, wordArray.toUint8Array(), this.bridge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompatRsaAdapter {
|
||||||
|
private bridge : CompatBridge
|
||||||
|
private publicKey : string = ''
|
||||||
|
private privateKey : string = ''
|
||||||
|
|
||||||
|
constructor(bridge : CompatBridge) {
|
||||||
|
this.bridge = bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
generateKeyPair() : CryptoRsaPair {
|
||||||
|
const pair = this.bridge.createRsaPair(2048)
|
||||||
|
this.publicKey = pair.publicKey
|
||||||
|
this.privateKey = pair.privateKey
|
||||||
|
return pair
|
||||||
|
}
|
||||||
|
|
||||||
|
setPublicKey(publicKey : string) : void {
|
||||||
|
this.publicKey = publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
setPrivateKey(privateKey : string) : void {
|
||||||
|
this.privateKey = privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypt(data : string) : string {
|
||||||
|
return this.bridge.rsaEncryptText(this.publicKey, data, 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypt(encryptedData : string) : string {
|
||||||
|
return this.bridge.rsaDecryptText(this.privateKey, encryptedData, 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
getPublicKey() : string {
|
||||||
|
return this.publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrivateKey() : string {
|
||||||
|
return this.privateKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompatSymmetricAdapter {
|
||||||
|
private bridge : CompatBridge
|
||||||
|
private algorithm : string
|
||||||
|
|
||||||
|
constructor(bridge : CompatBridge, algorithm : string) {
|
||||||
|
this.bridge = bridge
|
||||||
|
this.algorithm = algorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypt(message : string, key : string | CompatWordArray, cfg : UTSJSONObject | null = null) : CompatCipherResult {
|
||||||
|
const keyText = keyToText(key, this.bridge)
|
||||||
|
if (this.algorithm == 'DES') {
|
||||||
|
const cipherText = this.bridge.desEncryptText(keyText, message, buildLimeDesOptions(cfg, this.bridge))
|
||||||
|
return new CompatCipherResult(cipherText, parseCodecBytes('base64', cipherText, this.bridge))
|
||||||
|
}
|
||||||
|
const cipherText = this.bridge.aesEncryptText(keyText, message, buildLimeTextOptions(cfg, this.bridge))
|
||||||
|
return new CompatCipherResult(cipherText, parseCodecBytes('base64', cipherText, this.bridge))
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypt(ciphertext : string, password : string | CompatWordArray, cfg : UTSJSONObject | null = null) : CompatWordArray {
|
||||||
|
const keyText = keyToText(password, this.bridge)
|
||||||
|
if (this.algorithm == 'DES') {
|
||||||
|
return new CompatWordArray(textToUtf8Bytes(this.bridge.desDecryptText(keyText, ciphertext, buildLimeDesOptions(cfg, this.bridge)), this.bridge))
|
||||||
|
}
|
||||||
|
return new CompatWordArray(textToUtf8Bytes(this.bridge.aesDecryptText(keyText, ciphertext, buildLimeTextOptions(cfg, this.bridge)), this.bridge))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompatCrypto {
|
||||||
|
enc : CompatEncSet
|
||||||
|
mode : CompatModeSet
|
||||||
|
pad : CompatPadSet
|
||||||
|
AES : CompatSymmetricAdapter
|
||||||
|
DES : CompatSymmetricAdapter
|
||||||
|
RSA : CompatRsaAdapter
|
||||||
|
private bridge : CompatBridge
|
||||||
|
|
||||||
|
constructor(bridge : CompatBridge) {
|
||||||
|
this.bridge = bridge
|
||||||
|
this.enc = {
|
||||||
|
Utf8: new CompatEncoder('utf8', bridge),
|
||||||
|
Hex: new CompatEncoder('hex', bridge),
|
||||||
|
Base64: new CompatEncoder('base64', bridge),
|
||||||
|
Latin1: new CompatEncoder('latin1', bridge),
|
||||||
|
Utf16: new CompatEncoder('utf16', bridge),
|
||||||
|
Base64url: new CompatEncoder('base64url', bridge)
|
||||||
|
} as CompatEncSet
|
||||||
|
this.mode = {
|
||||||
|
CBC: LIME_MODE_CBC,
|
||||||
|
CFB: LIME_MODE_CFB,
|
||||||
|
CTR: LIME_MODE_CTR,
|
||||||
|
CTRGladman: LIME_MODE_CTR_GLADMAN,
|
||||||
|
ECB: LIME_MODE_ECB,
|
||||||
|
OFB: LIME_MODE_OFB
|
||||||
|
} as CompatModeSet
|
||||||
|
this.pad = {
|
||||||
|
Pkcs7: LIME_PAD_PKCS7,
|
||||||
|
AnsiX923: LIME_PAD_ANSI_X923,
|
||||||
|
Iso10126: LIME_PAD_ISO_10126,
|
||||||
|
Iso97971: LIME_PAD_ISO_97971,
|
||||||
|
NoPadding: LIME_PAD_NONE,
|
||||||
|
ZeroPadding: LIME_PAD_ZERO
|
||||||
|
} as CompatPadSet
|
||||||
|
this.AES = new CompatSymmetricAdapter(bridge, 'AES')
|
||||||
|
this.DES = new CompatSymmetricAdapter(bridge, 'DES')
|
||||||
|
this.RSA = new CompatRsaAdapter(bridge)
|
||||||
|
}
|
||||||
|
|
||||||
|
MD5(message : string) : CompatWordArray {
|
||||||
|
return new CompatWordArray(hexToBytes(this.bridge.digestText('md5', message)))
|
||||||
|
}
|
||||||
|
|
||||||
|
SHA1(message : string) : CompatWordArray {
|
||||||
|
return new CompatWordArray(hexToBytes(this.bridge.digestText('sha1', message)))
|
||||||
|
}
|
||||||
|
|
||||||
|
SHA256(message : string) : CompatWordArray {
|
||||||
|
return new CompatWordArray(hexToBytes(this.bridge.digestText('sha256', message)))
|
||||||
|
}
|
||||||
|
|
||||||
|
SHA512(message : string) : CompatWordArray {
|
||||||
|
return new CompatWordArray(hexToBytes(this.bridge.digestText('sha512', message)))
|
||||||
|
}
|
||||||
|
|
||||||
|
HmacSHA256(message : string, secretKey : string | CompatWordArray) : CompatWordArray {
|
||||||
|
return new CompatWordArray(hexToBytes(this.bridge.signText('sha256', typeof secretKey == 'string' ? secretKey : keyToText(secretKey, this.bridge), message)))
|
||||||
|
}
|
||||||
|
|
||||||
|
HmacSHA1(message : string, secretKey : string | CompatWordArray) : CompatWordArray {
|
||||||
|
return new CompatWordArray(hexToBytes(this.bridge.signText('sha1', typeof secretKey == 'string' ? secretKey : keyToText(secretKey, this.bridge), message)))
|
||||||
|
}
|
||||||
|
|
||||||
|
HmacSHA512(message : string, secretKey : string | CompatWordArray) : CompatWordArray {
|
||||||
|
return new CompatWordArray(hexToBytes(this.bridge.signText('sha512', typeof secretKey == 'string' ? secretKey : keyToText(secretKey, this.bridge), message)))
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(_callback : (err : IUniError) => void) : void {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCompatCrypto(bridge : CompatBridge) : CompatCrypto {
|
||||||
|
return new CompatCrypto(bridge)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64EncodeCompat(bridge : CompatBridge, input : string) : string {
|
||||||
|
return bridge.encodeText('base64', input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64DecodeCompat(bridge : CompatBridge, input : string) : string {
|
||||||
|
return bridge.decodeText('base64', input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function md5Compat(bridge : CompatBridge, input : string) : string {
|
||||||
|
return bridge.digestText('md5', input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha1Compat(bridge : CompatBridge, input : string) : string {
|
||||||
|
return bridge.digestText('sha1', input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha256Compat(bridge : CompatBridge, input : string) : string {
|
||||||
|
return bridge.digestText('sha256', input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha512Compat(bridge : CompatBridge, input : string) : string {
|
||||||
|
return bridge.digestText('sha512', input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha1Compat(bridge : CompatBridge, key : string, data : string) : string {
|
||||||
|
return bridge.signText('sha1', key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha256Compat(bridge : CompatBridge, key : string, data : string) : string {
|
||||||
|
return bridge.signText('sha256', key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha512Compat(bridge : CompatBridge, key : string, data : string) : string {
|
||||||
|
return bridge.signText('sha512', key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptCompat(bridge : CompatBridge, key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return bridge.aesEncryptText(key, data, buildXTextOptions(mode, iv, keySize))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptCompat(bridge : CompatBridge, key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return bridge.aesDecryptText(key, data, buildXTextOptions(mode, iv, keySize))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptBytesCompat(bridge : CompatBridge, key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return bridge.aesEncryptBytes(key, data, buildXByteOptions(mode, iv, keySize))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptBytesCompat(bridge : CompatBridge, key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return bridge.aesDecryptBytes(key, data, buildXByteOptions(mode, iv, keySize))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncryptCompat(bridge : CompatBridge, key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return bridge.desEncryptText(key, data, buildXDesOptions(mode, iv))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecryptCompat(bridge : CompatBridge, key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return bridge.desDecryptText(key, data, buildXDesOptions(mode, iv))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateRsaKeyPairCompat(bridge : CompatBridge, keySize : number = 2048) : CryptoRsaPair {
|
||||||
|
return bridge.createRsaPair(keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncryptCompat(bridge : CompatBridge, publicKey : string, data : string) : string {
|
||||||
|
return bridge.rsaEncryptText(publicKey, data, 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecryptCompat(bridge : CompatBridge, privateKey : string, data : string) : string {
|
||||||
|
return bridge.rsaDecryptText(privateKey, data, 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4EncryptCompat(bridge : CompatBridge, key : string, data : string) : string {
|
||||||
|
return bridge.rc4EncryptText(key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4DecryptCompat(bridge : CompatBridge, key : string, data : string) : string {
|
||||||
|
return bridge.rc4DecryptText(key, data)
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import type { CryptoBlockMode, CryptoByteCipherOptions, CryptoDesOptions, CryptoPaddingKind, CryptoRuntimeSnapshot, CryptoTextCipherOptions, CryptoTripleDesOptions, RsaOutputKind } from "../interface.uts"
|
||||||
|
|
||||||
|
export function createDefaultTextCipherOptions() : CryptoTextCipherOptions {
|
||||||
|
return {
|
||||||
|
mode: "CBC",
|
||||||
|
padding: "PKCS7",
|
||||||
|
ivText: "0123456789abcdef",
|
||||||
|
keyLength: 16
|
||||||
|
} as CryptoTextCipherOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDefaultByteCipherOptions() : CryptoByteCipherOptions {
|
||||||
|
return {
|
||||||
|
mode: "CBC",
|
||||||
|
padding: "PKCS7",
|
||||||
|
ivBytes: new Uint8Array([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102]),
|
||||||
|
keyLength: 16
|
||||||
|
} as CryptoByteCipherOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDefaultTripleDesOptions() : CryptoTripleDesOptions {
|
||||||
|
return {
|
||||||
|
mode: "CBC",
|
||||||
|
ivText: "12345678"
|
||||||
|
} as CryptoTripleDesOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDefaultDesOptions() : CryptoDesOptions {
|
||||||
|
return {
|
||||||
|
mode: "CBC",
|
||||||
|
padding: "PKCS7",
|
||||||
|
ivText: "12345678"
|
||||||
|
} as CryptoDesOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeTextCipherOptions(options : CryptoTextCipherOptions | null) : CryptoTextCipherOptions {
|
||||||
|
if (options == null) {
|
||||||
|
return createDefaultTextCipherOptions()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mode: options.mode,
|
||||||
|
padding: options.padding,
|
||||||
|
ivText: options.ivText,
|
||||||
|
keyLength: options.keyLength
|
||||||
|
} as CryptoTextCipherOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeByteCipherOptions(options : CryptoByteCipherOptions | null) : CryptoByteCipherOptions {
|
||||||
|
if (options == null) {
|
||||||
|
return createDefaultByteCipherOptions()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mode: options.mode,
|
||||||
|
padding: options.padding,
|
||||||
|
ivBytes: options.ivBytes,
|
||||||
|
keyLength: options.keyLength
|
||||||
|
} as CryptoByteCipherOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeTripleDesOptions(options : CryptoTripleDesOptions | null) : CryptoTripleDesOptions {
|
||||||
|
if (options == null) {
|
||||||
|
return createDefaultTripleDesOptions()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mode: options.mode,
|
||||||
|
ivText: options.ivText
|
||||||
|
} as CryptoTripleDesOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeDesOptions(options : CryptoDesOptions | null) : CryptoDesOptions {
|
||||||
|
if (options == null) {
|
||||||
|
return createDefaultDesOptions()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mode: options.mode,
|
||||||
|
padding: options.padding,
|
||||||
|
ivText: options.ivText
|
||||||
|
} as CryptoDesOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildRuntimeSnapshot(platformName : string, engineName : string, supportsAdvancedModes : boolean, usesNativeBridge : boolean) : CryptoRuntimeSnapshot {
|
||||||
|
return {
|
||||||
|
platformName: platformName,
|
||||||
|
engineName: engineName,
|
||||||
|
supportsAdvancedModes: supportsAdvancedModes,
|
||||||
|
usesNativeBridge: usesNativeBridge
|
||||||
|
} as CryptoRuntimeSnapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveHexOutput(outputKind : RsaOutputKind | null) : boolean {
|
||||||
|
return outputKind == "hex"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAsciiBlock(text : string, size : number) : Uint8Array {
|
||||||
|
const output = new Uint8Array(size)
|
||||||
|
let index = 0
|
||||||
|
while (index < size) {
|
||||||
|
if (index < text.length) {
|
||||||
|
output[index] = text.charCodeAt(index) & 0xff
|
||||||
|
} else {
|
||||||
|
output[index] = 0
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
@@ -0,0 +1,425 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import CryptoES from '../deps/node_modules/crypto-es/lib/index.js'
|
||||||
|
import JSEncrypt from '../deps/node_modules/jsencrypt/lib/index.js'
|
||||||
|
|
||||||
|
function ensureSupportedCodec(codec: string): void {
|
||||||
|
const value = codec.toLowerCase()
|
||||||
|
if (value == 'utf8' || value == 'hex' || value == 'base64' || value == 'base64url' || value == 'latin1' || value == 'utf16') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw new Error('不支持的编码类型')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDigestKind(kind: string): string {
|
||||||
|
const value = kind.toLowerCase()
|
||||||
|
if (value == 'md5' || value == 'sha1' || value == 'sha256' || value == 'sha512') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
throw new Error('不支持的摘要算法')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureMacKind(kind: string): string {
|
||||||
|
const value = kind.toLowerCase()
|
||||||
|
if (value == 'sha1' || value == 'sha256' || value == 'sha512') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
throw new Error('不支持的HMAC算法')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAesMode(mode: string): any {
|
||||||
|
const value = mode.toUpperCase()
|
||||||
|
if (value == 'CBC') {
|
||||||
|
return CryptoES.mode.CBC
|
||||||
|
}
|
||||||
|
if (value == 'CFB') {
|
||||||
|
return CryptoES.mode.CFB
|
||||||
|
}
|
||||||
|
if (value == 'CTR') {
|
||||||
|
return CryptoES.mode.CTR
|
||||||
|
}
|
||||||
|
if (value == 'CTRGLADMAN') {
|
||||||
|
return CryptoES.mode.CTRGladman
|
||||||
|
}
|
||||||
|
if (value == 'ECB') {
|
||||||
|
return CryptoES.mode.ECB
|
||||||
|
}
|
||||||
|
if (value == 'OFB') {
|
||||||
|
return CryptoES.mode.OFB
|
||||||
|
}
|
||||||
|
throw new Error('不支持的AES模式')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAesPadding(padding: string): any {
|
||||||
|
const value = padding.toUpperCase()
|
||||||
|
if (value == 'PKCS7') {
|
||||||
|
return CryptoES.pad.Pkcs7
|
||||||
|
}
|
||||||
|
if (value == 'ANSI_X923') {
|
||||||
|
return CryptoES.pad.AnsiX923
|
||||||
|
}
|
||||||
|
if (value == 'ISO_10126') {
|
||||||
|
return CryptoES.pad.Iso10126
|
||||||
|
}
|
||||||
|
if (value == 'ISO_97971') {
|
||||||
|
return CryptoES.pad.Iso97971
|
||||||
|
}
|
||||||
|
if (value == 'NONE') {
|
||||||
|
return CryptoES.pad.NoPadding
|
||||||
|
}
|
||||||
|
if (value == 'ZERO') {
|
||||||
|
return CryptoES.pad.ZeroPadding
|
||||||
|
}
|
||||||
|
throw new Error('不支持的AES填充')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureTripleDesMode(mode: string): any {
|
||||||
|
const value = mode.toUpperCase()
|
||||||
|
if (value == 'CBC') {
|
||||||
|
return CryptoES.mode.CBC
|
||||||
|
}
|
||||||
|
if (value == 'ECB') {
|
||||||
|
return CryptoES.mode.ECB
|
||||||
|
}
|
||||||
|
throw new Error('不支持的3DES模式')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDesMode(mode: string): any {
|
||||||
|
const value = mode.toUpperCase()
|
||||||
|
if (value == 'CBC') {
|
||||||
|
return CryptoES.mode.CBC
|
||||||
|
}
|
||||||
|
if (value == 'CFB') {
|
||||||
|
return CryptoES.mode.CFB
|
||||||
|
}
|
||||||
|
if (value == 'CTR') {
|
||||||
|
return CryptoES.mode.CTR
|
||||||
|
}
|
||||||
|
if (value == 'CTRGLADMAN') {
|
||||||
|
return CryptoES.mode.CTRGladman
|
||||||
|
}
|
||||||
|
if (value == 'ECB') {
|
||||||
|
return CryptoES.mode.ECB
|
||||||
|
}
|
||||||
|
if (value == 'OFB') {
|
||||||
|
return CryptoES.mode.OFB
|
||||||
|
}
|
||||||
|
throw new Error('不支持的DES模式')
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferKeyLength(sourceLength: number, hint: number | null): number {
|
||||||
|
if (hint != null) {
|
||||||
|
if (hint == 16 || hint == 24 || hint == 32) {
|
||||||
|
return hint
|
||||||
|
}
|
||||||
|
if (hint == 128) {
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
if (hint == 192) {
|
||||||
|
return 24
|
||||||
|
}
|
||||||
|
if (hint == 256) {
|
||||||
|
return 32
|
||||||
|
}
|
||||||
|
throw new Error('AES密钥长度无效')
|
||||||
|
}
|
||||||
|
if (sourceLength <= 16) {
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
if (sourceLength <= 24) {
|
||||||
|
return 24
|
||||||
|
}
|
||||||
|
return 32
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampBytes(source: Uint8Array, targetLength: number): Uint8Array {
|
||||||
|
const output = new Uint8Array(targetLength)
|
||||||
|
const actualLength = source.length < targetLength ? source.length : targetLength
|
||||||
|
let index = 0
|
||||||
|
while (index < actualLength) {
|
||||||
|
output[index] = source[index]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function textToUtf8Bytes(input: string): Uint8Array {
|
||||||
|
const wordArray = CryptoES.enc.Utf8.parse(input)
|
||||||
|
return wordArrayToBytes(wordArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
function utf8BytesToText(input: Uint8Array): string {
|
||||||
|
return bytesToWordArray(input).toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytesToWordArray(input: Uint8Array): any {
|
||||||
|
const words: number[] = []
|
||||||
|
let index = 0
|
||||||
|
while (index < input.length) {
|
||||||
|
let word = 0
|
||||||
|
let offset = 0
|
||||||
|
while (offset < 4 && index + offset < input.length) {
|
||||||
|
word |= input[index + offset] << (24 - offset * 8)
|
||||||
|
offset += 1
|
||||||
|
}
|
||||||
|
words.push(word)
|
||||||
|
index += 4
|
||||||
|
}
|
||||||
|
return CryptoES.lib.WordArray.create(words, input.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function wordArrayToBytes(wordArray: any): Uint8Array {
|
||||||
|
const sigBytes = wordArray.sigBytes
|
||||||
|
const output = new Uint8Array(sigBytes)
|
||||||
|
const words = wordArray.words
|
||||||
|
let index = 0
|
||||||
|
while (index < sigBytes) {
|
||||||
|
const wordIndex = Math.floor(index / 4)
|
||||||
|
const byteOffset = index % 4
|
||||||
|
output[index] = (words[wordIndex] >>> (24 - byteOffset * 8)) & 0xff
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitKeyText(secretText: string, keyLength: number | null): any {
|
||||||
|
const rawBytes = textToUtf8Bytes(secretText)
|
||||||
|
const actualLength = inferKeyLength(rawBytes.length, keyLength)
|
||||||
|
return bytesToWordArray(clampBytes(rawBytes, actualLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitFixedTextKey(secretText: string, targetLength: number): any {
|
||||||
|
return bytesToWordArray(clampBytes(textToUtf8Bytes(secretText), targetLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitKeyBytes(secretBytes: Uint8Array, keyLength: number | null): any {
|
||||||
|
const actualLength = inferKeyLength(secretBytes.length, keyLength)
|
||||||
|
return bytesToWordArray(clampBytes(secretBytes, actualLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitIvText(ivText: string | null, size: number): any {
|
||||||
|
const source = ivText == null ? new Uint8Array(size) : clampBytes(textToUtf8Bytes(ivText), size)
|
||||||
|
return bytesToWordArray(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fitIvBytes(ivBytes: Uint8Array | null, size: number): any {
|
||||||
|
const source = ivBytes == null ? new Uint8Array(size) : clampBytes(ivBytes, size)
|
||||||
|
return bytesToWordArray(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapCipherBytes(cipherBytes: Uint8Array): any {
|
||||||
|
return CryptoES.lib.CipherParams.create({
|
||||||
|
ciphertext: bytesToWordArray(cipherBytes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBase64Url(base64Text: string): string {
|
||||||
|
return base64Text.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromBase64Url(base64UrlText: string): string {
|
||||||
|
let output = base64UrlText.replace(/-/g, '+').replace(/_/g, '/')
|
||||||
|
while (output.length % 4 != 0) {
|
||||||
|
output += '='
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64ToHex(base64Text: string): string {
|
||||||
|
return CryptoES.enc.Base64.parse(base64Text).toString(CryptoES.enc.Hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToBase64(hexText: string): string {
|
||||||
|
return CryptoES.enc.Hex.parse(hexText).toString(CryptoES.enc.Base64)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeTextPortable(codec: string, plainText: string): string {
|
||||||
|
ensureSupportedCodec(codec)
|
||||||
|
const value = codec.toLowerCase()
|
||||||
|
if (value == 'utf8') {
|
||||||
|
return plainText
|
||||||
|
}
|
||||||
|
const word = CryptoES.enc.Utf8.parse(plainText)
|
||||||
|
if (value == 'hex') {
|
||||||
|
return CryptoES.enc.Hex.stringify(word)
|
||||||
|
}
|
||||||
|
if (value == 'base64') {
|
||||||
|
return CryptoES.enc.Base64.stringify(word)
|
||||||
|
}
|
||||||
|
if (value == 'base64url') {
|
||||||
|
return toBase64Url(CryptoES.enc.Base64.stringify(word))
|
||||||
|
}
|
||||||
|
if (value == 'latin1') {
|
||||||
|
return CryptoES.enc.Latin1.stringify(word)
|
||||||
|
}
|
||||||
|
return CryptoES.enc.Utf16.stringify(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeTextPortable(codec: string, encodedText: string): string {
|
||||||
|
ensureSupportedCodec(codec)
|
||||||
|
const value = codec.toLowerCase()
|
||||||
|
if (value == 'utf8') {
|
||||||
|
return encodedText
|
||||||
|
}
|
||||||
|
if (value == 'hex') {
|
||||||
|
return CryptoES.enc.Hex.parse(encodedText).toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
if (value == 'base64') {
|
||||||
|
return CryptoES.enc.Base64.parse(encodedText).toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
if (value == 'base64url') {
|
||||||
|
return CryptoES.enc.Base64.parse(fromBase64Url(encodedText)).toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
if (value == 'latin1') {
|
||||||
|
return CryptoES.enc.Latin1.parse(encodedText).toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
return CryptoES.enc.Utf16.parse(encodedText).toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function digestTextPortable(kind: string, plainText: string): string {
|
||||||
|
const value = ensureDigestKind(kind)
|
||||||
|
if (value == 'md5') {
|
||||||
|
return CryptoES.MD5(plainText).toString()
|
||||||
|
}
|
||||||
|
if (value == 'sha1') {
|
||||||
|
return CryptoES.SHA1(plainText).toString()
|
||||||
|
}
|
||||||
|
if (value == 'sha256') {
|
||||||
|
return CryptoES.SHA256(plainText).toString()
|
||||||
|
}
|
||||||
|
return CryptoES.SHA512(plainText).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signTextPortable(kind: string, secretText: string, plainText: string): string {
|
||||||
|
const value = ensureMacKind(kind)
|
||||||
|
if (value == 'sha1') {
|
||||||
|
return CryptoES.HmacSHA1(plainText, secretText).toString()
|
||||||
|
}
|
||||||
|
if (value == 'sha256') {
|
||||||
|
return CryptoES.HmacSHA256(plainText, secretText).toString()
|
||||||
|
}
|
||||||
|
return CryptoES.HmacSHA512(plainText, secretText).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptTextPortable(secretText: string, plainText: string, mode: string, padding: string, ivText: string | null, keyLength: number | null): string {
|
||||||
|
const encrypted = CryptoES.AES.encrypt(plainText, fitKeyText(secretText, keyLength), {
|
||||||
|
iv: fitIvText(ivText, 16),
|
||||||
|
mode: ensureAesMode(mode),
|
||||||
|
padding: ensureAesPadding(padding)
|
||||||
|
})
|
||||||
|
return encrypted.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptTextPortable(secretText: string, cipherText: string, mode: string, padding: string, ivText: string | null, keyLength: number | null): string {
|
||||||
|
const decrypted = CryptoES.AES.decrypt(cipherText, fitKeyText(secretText, keyLength), {
|
||||||
|
iv: fitIvText(ivText, 16),
|
||||||
|
mode: ensureAesMode(mode),
|
||||||
|
padding: ensureAesPadding(padding)
|
||||||
|
})
|
||||||
|
return decrypted.toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptBytesPortable(secretBytes: Uint8Array, plainBytes: Uint8Array, mode: string, padding: string, ivBytes: Uint8Array | null, keyLength: number | null): Uint8Array {
|
||||||
|
const encrypted = CryptoES.AES.encrypt(bytesToWordArray(plainBytes), fitKeyBytes(secretBytes, keyLength), {
|
||||||
|
iv: fitIvBytes(ivBytes, 16),
|
||||||
|
mode: ensureAesMode(mode),
|
||||||
|
padding: ensureAesPadding(padding)
|
||||||
|
})
|
||||||
|
return wordArrayToBytes(encrypted.ciphertext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptBytesPortable(secretBytes: Uint8Array, cipherBytes: Uint8Array, mode: string, padding: string, ivBytes: Uint8Array | null, keyLength: number | null): Uint8Array {
|
||||||
|
const decrypted = CryptoES.AES.decrypt(wrapCipherBytes(cipherBytes), fitKeyBytes(secretBytes, keyLength), {
|
||||||
|
iv: fitIvBytes(ivBytes, 16),
|
||||||
|
mode: ensureAesMode(mode),
|
||||||
|
padding: ensureAesPadding(padding)
|
||||||
|
})
|
||||||
|
return wordArrayToBytes(decrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesEncryptTextPortable(secretText: string, plainText: string, mode: string, ivText: string | null): string {
|
||||||
|
const encrypted = CryptoES.TripleDES.encrypt(plainText, fitKeyText(secretText, 24), {
|
||||||
|
iv: fitIvText(ivText, 8),
|
||||||
|
mode: ensureTripleDesMode(mode),
|
||||||
|
padding: CryptoES.pad.Pkcs7
|
||||||
|
})
|
||||||
|
return encrypted.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesDecryptTextPortable(secretText: string, cipherText: string, mode: string, ivText: string | null): string {
|
||||||
|
const decrypted = CryptoES.TripleDES.decrypt(cipherText, fitKeyText(secretText, 24), {
|
||||||
|
iv: fitIvText(ivText, 8),
|
||||||
|
mode: ensureTripleDesMode(mode),
|
||||||
|
padding: CryptoES.pad.Pkcs7
|
||||||
|
})
|
||||||
|
return decrypted.toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncryptTextPortable(secretText: string, plainText: string, mode: string, padding: string, ivText: string | null): string {
|
||||||
|
const encrypted = CryptoES.DES.encrypt(plainText, fitFixedTextKey(secretText, 8), {
|
||||||
|
iv: fitIvText(ivText, 8),
|
||||||
|
mode: ensureDesMode(mode),
|
||||||
|
padding: ensureAesPadding(padding)
|
||||||
|
})
|
||||||
|
return encrypted.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecryptTextPortable(secretText: string, cipherText: string, mode: string, padding: string, ivText: string | null): string {
|
||||||
|
const decrypted = CryptoES.DES.decrypt(cipherText, fitFixedTextKey(secretText, 8), {
|
||||||
|
iv: fitIvText(ivText, 8),
|
||||||
|
mode: ensureDesMode(mode),
|
||||||
|
padding: ensureAesPadding(padding)
|
||||||
|
})
|
||||||
|
return decrypted.toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4EncryptTextPortable(secretText: string, plainText: string): string {
|
||||||
|
const encrypted = CryptoES.RC4.encrypt(plainText, CryptoES.enc.Utf8.parse(secretText))
|
||||||
|
return encrypted.ciphertext.toString(CryptoES.enc.Hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4DecryptTextPortable(secretText: string, cipherHexText: string): string {
|
||||||
|
const decrypted = CryptoES.RC4.decrypt(wrapCipherBytes(wordArrayToBytes(CryptoES.enc.Hex.parse(cipherHexText))), CryptoES.enc.Utf8.parse(secretText))
|
||||||
|
return decrypted.toString(CryptoES.enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRsaPairPortable(keySize: number): { publicKey: string, privateKey: string } {
|
||||||
|
const engine = new JSEncrypt({ default_key_size: keySize.toString() })
|
||||||
|
engine.getKey()
|
||||||
|
return {
|
||||||
|
publicKey: engine.getPublicKey(),
|
||||||
|
privateKey: engine.getPrivateKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncryptTextPortable(publicKey: string, plainText: string, outputKind: string | null): string {
|
||||||
|
const engine = new JSEncrypt()
|
||||||
|
engine.setPublicKey(publicKey)
|
||||||
|
const encrypted = engine.encrypt(plainText)
|
||||||
|
if (encrypted == null || encrypted == false) {
|
||||||
|
throw new Error('RSA加密失败')
|
||||||
|
}
|
||||||
|
return outputKind == 'hex' ? base64ToHex(encrypted as string) : encrypted as string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecryptTextPortable(privateKey: string, cipherText: string, outputKind: string | null): string {
|
||||||
|
const engine = new JSEncrypt()
|
||||||
|
engine.setPrivateKey(privateKey)
|
||||||
|
const source = outputKind == 'hex' ? hexToBase64(cipherText) : cipherText
|
||||||
|
const decrypted = engine.decrypt(source)
|
||||||
|
if (decrypted == null || decrypted == false) {
|
||||||
|
throw new Error('RSA解密失败')
|
||||||
|
}
|
||||||
|
return decrypted as string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bytesToHexPortable(input: Uint8Array): string {
|
||||||
|
return bytesToWordArray(input).toString(CryptoES.enc.Hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function utf8ToBytesPortable(input: string): Uint8Array {
|
||||||
|
return textToUtf8Bytes(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bytesToUtf8Portable(input: Uint8Array): string {
|
||||||
|
return utf8BytesToText(input)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { CryptoBridgeErrorCode, CryptoBridgeFailure } from "./interface.uts"
|
||||||
|
|
||||||
|
export const CryptoErrorSubject = "laoqianjunzi-crypto"
|
||||||
|
|
||||||
|
export const CryptoErrorMessages : Map<CryptoBridgeErrorCode, string> = new Map([
|
||||||
|
[7401, "crypto bridge: text input is empty"],
|
||||||
|
[7402, "crypto bridge: unsupported codec kind"],
|
||||||
|
[7403, "crypto bridge: unsupported digest kind"],
|
||||||
|
[7404, "crypto bridge: unsupported hmac kind"],
|
||||||
|
[7405, "crypto bridge: unsupported block mode"],
|
||||||
|
[7406, "crypto bridge: unsupported padding kind"],
|
||||||
|
[7407, "crypto bridge: invalid cipher payload"],
|
||||||
|
[7408, "crypto bridge: invalid key or iv"],
|
||||||
|
[7409, "crypto bridge: rsa operation failed"],
|
||||||
|
[7410, "crypto bridge: current platform is missing required capability"]
|
||||||
|
])
|
||||||
|
|
||||||
|
export class CryptoBridgeFailureImpl extends UniError implements CryptoBridgeFailure {
|
||||||
|
override errCode : CryptoBridgeErrorCode
|
||||||
|
|
||||||
|
constructor(errCode : CryptoBridgeErrorCode) {
|
||||||
|
super()
|
||||||
|
this.errSubject = CryptoErrorSubject
|
||||||
|
this.errCode = errCode
|
||||||
|
this.errMsg = CryptoErrorMessages[errCode] ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import type { CryptoByteCipherOptions, CryptoCodecKind, CryptoDesOptions, CryptoDigestKind, CryptoMacKind, CryptoRsaPair, CryptoRuntimeSnapshot, CryptoTextCipherOptions, CryptoTripleDesOptions, RsaOutputKind } from '../interface.uts'
|
||||||
|
import { buildRuntimeSnapshot, createDefaultByteCipherOptions, createDefaultDesOptions, createDefaultTextCipherOptions, createDefaultTripleDesOptions, normalizeByteCipherOptions, normalizeDesOptions, normalizeTextCipherOptions, normalizeTripleDesOptions, resolveHexOutput } from '../shared/options.uts'
|
||||||
|
import { aesDecryptBytesPortable, aesDecryptTextPortable, aesEncryptBytesPortable, aesEncryptTextPortable, createRsaPairPortable, decodeTextPortable, desDecryptTextPortable, desEncryptTextPortable, digestTextPortable, encodeTextPortable, rc4DecryptTextPortable, rc4EncryptTextPortable, rsaDecryptTextPortable, rsaEncryptTextPortable, signTextPortable, tripleDesDecryptTextPortable, tripleDesEncryptTextPortable } from '../shared/portable_crypto'
|
||||||
|
import type { CompatBridge, CompatCrypto } from '../shared/compat.uts'
|
||||||
|
import { aesDecryptBytesCompat, aesDecryptCompat, aesEncryptBytesCompat, aesEncryptCompat, base64DecodeCompat, base64EncodeCompat, createCompatBridge, createCompatCrypto, desDecryptCompat, desEncryptCompat, generateRsaKeyPairCompat, hmacSha1Compat, hmacSha256Compat, hmacSha512Compat, md5Compat, rc4DecryptCompat, rc4EncryptCompat, rsaDecryptCompat, rsaEncryptCompat, sha1Compat, sha256Compat, sha512Compat } from '../shared/compat.uts'
|
||||||
|
|
||||||
|
export function defaultTextCipherOptions() : CryptoTextCipherOptions {
|
||||||
|
return createDefaultTextCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultByteCipherOptions() : CryptoByteCipherOptions {
|
||||||
|
return createDefaultByteCipherOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultTripleDesOptions() : CryptoTripleDesOptions {
|
||||||
|
return createDefaultTripleDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultDesOptions() : CryptoDesOptions {
|
||||||
|
return createDefaultDesOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function describeCryptoRuntime() : CryptoRuntimeSnapshot {
|
||||||
|
return buildRuntimeSnapshot('Web', 'crypto-es', true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeText(codec : CryptoCodecKind, plainText : string) : string {
|
||||||
|
return encodeTextPortable(codec, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeText(codec : CryptoCodecKind, encodedText : string) : string {
|
||||||
|
return decodeTextPortable(codec, encodedText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function digestText(kind : CryptoDigestKind, plainText : string) : string {
|
||||||
|
return digestTextPortable(kind, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signText(kind : CryptoMacKind, secretText : string, plainText : string) : string {
|
||||||
|
return signTextPortable(kind, secretText, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptText(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
return aesEncryptTextPortable(secretText, plainText, actual.mode, actual.padding, actual.ivText, actual.keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptText(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string {
|
||||||
|
const actual = normalizeTextCipherOptions(options)
|
||||||
|
return aesDecryptTextPortable(secretText, cipherText, actual.mode, actual.padding, actual.ivText, actual.keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncryptBytes(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
return aesEncryptBytesPortable(secretBytes, plainBytes, actual.mode, actual.padding, actual.ivBytes, actual.keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecryptBytes(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array {
|
||||||
|
const actual = normalizeByteCipherOptions(options)
|
||||||
|
return aesDecryptBytesPortable(secretBytes, cipherBytes, actual.mode, actual.padding, actual.ivBytes, actual.keyLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesEncryptText(secretText : string, plainText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
return tripleDesEncryptTextPortable(secretText, plainText, actual.mode, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tripleDesDecryptText(secretText : string, cipherText : string, options : CryptoTripleDesOptions | null) : string {
|
||||||
|
const actual = normalizeTripleDesOptions(options)
|
||||||
|
return tripleDesDecryptTextPortable(secretText, cipherText, actual.mode, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncryptText(secretText : string, plainText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
return desEncryptTextPortable(secretText, plainText, actual.mode, actual.padding, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecryptText(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string {
|
||||||
|
const actual = normalizeDesOptions(options)
|
||||||
|
return desDecryptTextPortable(secretText, cipherText, actual.mode, actual.padding, actual.ivText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4EncryptText(secretText : string, plainText : string) : string {
|
||||||
|
return rc4EncryptTextPortable(secretText, plainText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4DecryptText(secretText : string, cipherHexText : string) : string {
|
||||||
|
return rc4DecryptTextPortable(secretText, cipherHexText)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRsaPair(keySize : number) : CryptoRsaPair {
|
||||||
|
const bundle = createRsaPairPortable(keySize)
|
||||||
|
return {
|
||||||
|
publicKey: bundle.publicKey,
|
||||||
|
privateKey: bundle.privateKey
|
||||||
|
} as CryptoRsaPair
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncryptText(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return rsaEncryptTextPortable(publicKey, plainText, resolveHexOutput(outputKind) ? 'hex' : 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecryptText(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string {
|
||||||
|
return rsaDecryptTextPortable(privateKey, cipherText, resolveHexOutput(outputKind) ? 'hex' : 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
function compatBridge() : CompatBridge {
|
||||||
|
return createCompatBridge(
|
||||||
|
(codec : CryptoCodecKind, plainText : string) : string => encodeText(codec, plainText),
|
||||||
|
(codec : CryptoCodecKind, encodedText : string) : string => decodeText(codec, encodedText),
|
||||||
|
(kind : CryptoDigestKind, plainText : string) : string => digestText(kind, plainText),
|
||||||
|
(kind : CryptoMacKind, secretText : string, plainText : string) : string => signText(kind, secretText, plainText),
|
||||||
|
(secretText : string, plainText : string, options : CryptoTextCipherOptions | null) : string => aesEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoTextCipherOptions | null) : string => aesDecryptText(secretText, cipherText, options),
|
||||||
|
(secretBytes : Uint8Array, plainBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesEncryptBytes(secretBytes, plainBytes, options),
|
||||||
|
(secretBytes : Uint8Array, cipherBytes : Uint8Array, options : CryptoByteCipherOptions | null) : Uint8Array => aesDecryptBytes(secretBytes, cipherBytes, options),
|
||||||
|
(secretText : string, plainText : string, options : CryptoDesOptions | null) : string => desEncryptText(secretText, plainText, options),
|
||||||
|
(secretText : string, cipherText : string, options : CryptoDesOptions | null) : string => desDecryptText(secretText, cipherText, options),
|
||||||
|
(keySize : number) : CryptoRsaPair => createRsaPair(keySize),
|
||||||
|
(publicKey : string, plainText : string, outputKind : RsaOutputKind | null) : string => rsaEncryptText(publicKey, plainText, outputKind),
|
||||||
|
(privateKey : string, cipherText : string, outputKind : RsaOutputKind | null) : string => rsaDecryptText(privateKey, cipherText, outputKind),
|
||||||
|
(secretText : string, plainText : string) : string => rc4EncryptText(secretText, plainText),
|
||||||
|
(secretText : string, cipherHexText : string) : string => rc4DecryptText(secretText, cipherHexText)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Encode(input : string) : string {
|
||||||
|
return base64EncodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64Decode(input : string) : string {
|
||||||
|
return base64DecodeCompat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function md5(input : string) : string {
|
||||||
|
return md5Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha1(input : string) : string {
|
||||||
|
return sha1Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha256(input : string) : string {
|
||||||
|
return sha256Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sha512(input : string) : string {
|
||||||
|
return sha512Compat(compatBridge(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha1(key : string, data : string) : string {
|
||||||
|
return hmacSha1Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha256(key : string, data : string) : string {
|
||||||
|
return hmacSha256Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hmacSha512(key : string, data : string) : string {
|
||||||
|
return hmacSha512Compat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesEncryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null, keySize : number | null = 16) : string {
|
||||||
|
return aesDecryptCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesEncrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesEncryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aesDecrypt2(key : Uint8Array, data : Uint8Array, mode : string = 'ECB', iv : Uint8Array | null = null, keySize : number | null = 16) : Uint8Array {
|
||||||
|
return aesDecryptBytesCompat(compatBridge(), key, data, mode, iv, keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desEncrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desEncryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function desDecrypt(key : string, data : string, mode : string = 'ECB', iv : string | null = null) : string {
|
||||||
|
return desDecryptCompat(compatBridge(), key, data, mode, iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateRSAKeyPair(keySize : number = 2048) : CryptoRsaPair {
|
||||||
|
return generateRsaKeyPairCompat(compatBridge(), keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaEncrypt(publicKey : string, data : string) : string {
|
||||||
|
return rsaEncryptCompat(compatBridge(), publicKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rsaDecrypt(privateKey : string, data : string) : string {
|
||||||
|
return rsaDecryptCompat(compatBridge(), privateKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Encrypt(key : string, data : string) : string {
|
||||||
|
return rc4EncryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rc4Decrypt(key : string, data : string) : string {
|
||||||
|
return rc4DecryptCompat(compatBridge(), key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCrypto() : CompatCrypto {
|
||||||
|
return createCompatCrypto(compatBridge())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user