diff --git a/uni_modules/laoqianjunzi-crypto/changelog.md b/uni_modules/laoqianjunzi-crypto/changelog.md new file mode 100644 index 0000000..23e8ff9 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/changelog.md @@ -0,0 +1,2 @@ +## 1.0.0(2026-05-30) +初次提交 diff --git a/uni_modules/laoqianjunzi-crypto/package.json b/uni_modules/laoqianjunzi-crypto/package.json new file mode 100644 index 0000000..db510b7 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/package.json @@ -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": "√" + } + } + } +} diff --git a/uni_modules/laoqianjunzi-crypto/pages/index.uvue b/uni_modules/laoqianjunzi-crypto/pages/index.uvue new file mode 100644 index 0000000..39934b3 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/pages/index.uvue @@ -0,0 +1,518 @@ + + + + + diff --git a/uni_modules/laoqianjunzi-crypto/readme.md b/uni_modules/laoqianjunzi-crypto/readme.md new file mode 100644 index 0000000..9df9147 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/readme.md @@ -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` 中注册该页面,可直接运行到对应页面进行体验。 diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/app-android/NativeCipherCore.kt b/uni_modules/laoqianjunzi-crypto/utssdk/app-android/NativeCipherCore.kt new file mode 100644 index 0000000..f751f82 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/app-android/NativeCipherCore.kt @@ -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 + } +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/app-android/config.json b/uni_modules/laoqianjunzi-crypto/utssdk/app-android/config.json new file mode 100644 index 0000000..5defc8f --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/app-android/config.json @@ -0,0 +1,3 @@ +{ + "minSdkVersion": "21" +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/app-android/index.uts b/uni_modules/laoqianjunzi-crypto/utssdk/app-android/index.uts new file mode 100644 index 0000000..9000246 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/app-android/index.uts @@ -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()) +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/app-harmony/config.json b/uni_modules/laoqianjunzi-crypto/utssdk/app-harmony/config.json new file mode 100644 index 0000000..ea289a4 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/app-harmony/config.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@ohos/crypto-js": "2.0.4" + } +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/app-harmony/index.uts b/uni_modules/laoqianjunzi-crypto/utssdk/app-harmony/index.uts new file mode 100644 index 0000000..2d8d623 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/app-harmony/index.uts @@ -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()) +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/app-harmony/native_rsa.ets b/uni_modules/laoqianjunzi-crypto/utssdk/app-harmony/native_rsa.ets new file mode 100644 index 0000000..03d7765 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/app-harmony/native_rsa.ets @@ -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 + } +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/app-ios/NativeCipherCore.swift b/uni_modules/laoqianjunzi-crypto/utssdk/app-ios/NativeCipherCore.swift new file mode 100644 index 0000000..5448d97 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/app-ios/NativeCipherCore.swift @@ -0,0 +1,1130 @@ +import Foundation +import CommonCrypto +import Security + +public struct NativeRsaBundle { + public let publicKey: String + public let privateKey: String + + public init(publicKey: String, privateKey: String) { + self.publicKey = publicKey + self.privateKey = privateKey + } +} + +public enum NativeCipherCore { + public static func digestHex(kind: String, plainText: String) throws -> String { + let input = Data(plainText.utf8) + let digest: Data + switch normalize(kind) { + case "MD5": + digest = hashData(input, length: Int(CC_MD5_DIGEST_LENGTH)) { buffer, bytes, count in + _ = CC_MD5(bytes, CC_LONG(count), buffer.bindMemory(to: UInt8.self).baseAddress) + } + case "SHA1": + digest = hashData(input, length: Int(CC_SHA1_DIGEST_LENGTH)) { buffer, bytes, count in + _ = CC_SHA1(bytes, CC_LONG(count), buffer.bindMemory(to: UInt8.self).baseAddress) + } + case "SHA256": + digest = hashData(input, length: Int(CC_SHA256_DIGEST_LENGTH)) { buffer, bytes, count in + _ = CC_SHA256(bytes, CC_LONG(count), buffer.bindMemory(to: UInt8.self).baseAddress) + } + case "SHA512": + digest = hashData(input, length: Int(CC_SHA512_DIGEST_LENGTH)) { buffer, bytes, count in + _ = CC_SHA512(bytes, CC_LONG(count), buffer.bindMemory(to: UInt8.self).baseAddress) + } + default: + throw cipherError("不支持的摘要算法") + } + return digest.hexString() + } + + public static func hmacHex(kind: String, secretText: String, plainText: String) throws -> String { + let algorithm: CCHmacAlgorithm + let digestLength: Int + switch normalize(kind) { + case "SHA1": + algorithm = CCHmacAlgorithm(kCCHmacAlgSHA1) + digestLength = Int(CC_SHA1_DIGEST_LENGTH) + case "SHA256": + algorithm = CCHmacAlgorithm(kCCHmacAlgSHA256) + digestLength = Int(CC_SHA256_DIGEST_LENGTH) + case "SHA512": + algorithm = CCHmacAlgorithm(kCCHmacAlgSHA512) + digestLength = Int(CC_SHA512_DIGEST_LENGTH) + default: + throw cipherError("不支持的HMAC算法") + } + + let keyData = Data(secretText.utf8) + let inputData = Data(plainText.utf8) + var output = Data(count: digestLength) + output.withUnsafeMutableBytes { outputBuffer in + keyData.withUnsafeBytes { keyBuffer in + inputData.withUnsafeBytes { inputBuffer in + CCHmac( + algorithm, + keyBuffer.baseAddress, + keyData.count, + inputBuffer.baseAddress, + inputData.count, + outputBuffer.baseAddress + ) + } + } + } + return output.hexString() + } + + public static func encodeBase64(plainText: String) -> String { + Data(plainText.utf8).base64EncodedString() + } + + public static func decodeBase64(encodedText: String) throws -> String { + guard let data = Data(base64Encoded: encodedText) else { + throw cipherError("Base64无效") + } + guard let text = String(data: data, encoding: .utf8) else { + throw cipherError("文本解码失败") + } + return text + } + + public static func encodeCodec(codec: String, plainText: String) throws -> String { + let input = Data(plainText.utf8) + switch normalize(codec) { + case "UTF8": + return plainText + case "HEX": + return input.hexString() + case "BASE64": + return input.base64EncodedString() + case "BASE64URL": + return input.base64EncodedString() + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "=+$", with: "", options: .regularExpression) + case "LATIN1": + return latin1String(from: input) + case "UTF16": + return utf16CodecString(from: input) + default: + throw cipherError("不支持的编码类型") + } + } + + public static func decodeCodec(codec: String, encodedText: String) throws -> String { + switch normalize(codec) { + case "UTF8": + return encodedText + case "HEX": + return try utf8String(from: Data(hexString: encodedText)) + case "BASE64": + return try decodeBase64(encodedText: encodedText) + case "BASE64URL": + var normalized = encodedText.replacingOccurrences(of: "-", with: "+") + .replacingOccurrences(of: "_", with: "/") + while normalized.count % 4 != 0 { + normalized.append("=") + } + return try decodeBase64(encodedText: normalized) + case "LATIN1": + return try utf8String(from: dataFromLatin1String(encodedText)) + case "UTF16": + return try utf8String(from: dataFromUtf16CodecString(encodedText)) + default: + throw cipherError("不支持的编码类型") + } + } + + public static func aesEncryptText(secretText: String, plainText: String, mode: String, padding: String, ivText: String?, keyLength: Int?) throws -> String { + let cipherBytes = try aesEncryptBytes( + secretBytes: Array(Data(secretText.utf8)), + plainBytes: Array(Data(plainText.utf8)), + mode: mode, + padding: padding, + ivBytes: ivText.map { Array(Data($0.utf8)) }, + keyLength: keyLength + ) + return Data(cipherBytes).base64EncodedString() + } + + public static func aesDecryptText(secretText: String, cipherText: String, mode: String, padding: String, ivText: String?, keyLength: Int?) throws -> String { + guard let cipherData = Data(base64Encoded: cipherText) else { + throw cipherError("Base64无效") + } + let plainBytes = try aesDecryptBytes( + secretBytes: Array(Data(secretText.utf8)), + cipherBytes: Array(cipherData), + mode: mode, + padding: padding, + ivBytes: ivText.map { Array(Data($0.utf8)) }, + keyLength: keyLength + ) + guard let text = String(data: Data(plainBytes), encoding: .utf8) else { + throw cipherError("文本解码失败") + } + return text + } + + public static func aesEncryptBytes(secretBytes: [UInt8], plainBytes: [UInt8], mode: String, padding: String, ivBytes: [UInt8]?, keyLength: Int?) throws -> [UInt8] { + let config = try makeAesConfig(mode: mode, padding: padding, keyLength: keyLength) + let key = try normalizeKey(secretBytes, to: config.keyLength) + let iv = try normalizeIV(ivBytes, blockSize: kCCBlockSizeAES128, mode: config.mode) + let plainData = Data(plainBytes) + let padded = try applyPaddingIfNeeded(plainData, blockSize: kCCBlockSizeAES128, padding: config.padding, mode: config.mode) + let encrypted = try cryptWithMode( + operation: CCOperation(kCCEncrypt), + algorithm: CCAlgorithm(kCCAlgorithmAES), + mode: config.mode.ccMode, + options: config.mode.ccOptions, + key: key, + iv: iv, + input: padded + ) + return Array(encrypted) + } + + public static func aesDecryptBytes(secretBytes: [UInt8], cipherBytes: [UInt8], mode: String, padding: String, ivBytes: [UInt8]?, keyLength: Int?) throws -> [UInt8] { + let config = try makeAesConfig(mode: mode, padding: padding, keyLength: keyLength) + let key = try normalizeKey(secretBytes, to: config.keyLength) + let iv = try normalizeIV(ivBytes, blockSize: kCCBlockSizeAES128, mode: config.mode) + let decrypted = try cryptWithMode( + operation: CCOperation(kCCDecrypt), + algorithm: CCAlgorithm(kCCAlgorithmAES), + mode: config.mode.ccMode, + options: config.mode.ccOptions, + key: key, + iv: iv, + input: Data(cipherBytes) + ) + let plainData = try removePaddingIfNeeded(decrypted, blockSize: kCCBlockSizeAES128, padding: config.padding, mode: config.mode) + return Array(plainData) + } + + public static func tripleDesEncryptText(secretText: String, plainText: String, mode: String, ivText: String?) throws -> String { + let cipherData = try tripleDesTransform( + operation: CCOperation(kCCEncrypt), + secretBytes: Array(Data(secretText.utf8)), + input: try applyPadding(Data(plainText.utf8), blockSize: kCCBlockSize3DES, padding: .pkcs7), + modeName: mode, + ivBytes: ivText.map { Array(Data($0.utf8)) } + ) + return cipherData.base64EncodedString() + } + + public static func tripleDesDecryptText(secretText: String, cipherText: String, mode: String, ivText: String?) throws -> String { + guard let cipherData = Data(base64Encoded: cipherText) else { + throw cipherError("Base64无效") + } + let plainData = try tripleDesTransform( + operation: CCOperation(kCCDecrypt), + secretBytes: Array(Data(secretText.utf8)), + input: cipherData, + modeName: mode, + ivBytes: ivText.map { Array(Data($0.utf8)) } + ) + let unpadded = try removePadding(plainData, blockSize: kCCBlockSize3DES, padding: .pkcs7) + guard let text = String(data: unpadded, encoding: .utf8) else { + throw cipherError("文本解码失败") + } + return text + } + + public static func desEncryptText(secretText: String, plainText: String, mode: String, padding: String, ivText: String?) throws -> String { + let config = try makeDesConfig(mode: mode, padding: padding) + let key = try normalizeKey(Array(Data(secretText.utf8)), to: kCCKeySizeDES) + let iv = try normalizeIV(ivText.map { Array(Data($0.utf8)) }, blockSize: kCCBlockSizeDES, mode: config.mode) + let padded = try applyPaddingIfNeeded(Data(plainText.utf8), blockSize: kCCBlockSizeDES, padding: config.padding, mode: config.mode) + let encrypted = try cryptWithMode( + operation: CCOperation(kCCEncrypt), + algorithm: CCAlgorithm(kCCAlgorithmDES), + mode: config.mode.ccMode, + options: config.mode.ccOptions, + key: key, + iv: iv, + input: padded + ) + return encrypted.base64EncodedString() + } + + public static func desDecryptText(secretText: String, cipherText: String, mode: String, padding: String, ivText: String?) throws -> String { + guard let cipherData = Data(base64Encoded: cipherText) else { + throw cipherError("Base64无效") + } + let config = try makeDesConfig(mode: mode, padding: padding) + let key = try normalizeKey(Array(Data(secretText.utf8)), to: kCCKeySizeDES) + let iv = try normalizeIV(ivText.map { Array(Data($0.utf8)) }, blockSize: kCCBlockSizeDES, mode: config.mode) + let decrypted = try cryptWithMode( + operation: CCOperation(kCCDecrypt), + algorithm: CCAlgorithm(kCCAlgorithmDES), + mode: config.mode.ccMode, + options: config.mode.ccOptions, + key: key, + iv: iv, + input: cipherData + ) + let plainData = try removePaddingIfNeeded(decrypted, blockSize: kCCBlockSizeDES, padding: config.padding, mode: config.mode) + return try utf8String(from: plainData) + } + + public static func rc4EncryptToHex(secretText: String, plainText: String) throws -> String { + let cipher = try rc4Transform(secret: Data(secretText.utf8), input: Data(plainText.utf8)) + return cipher.hexString() + } + + public static func rc4DecryptFromHex(secretText: String, cipherHexText: String) throws -> String { + let cipher = try Data(hexString: cipherHexText) + let plain = try rc4Transform(secret: Data(secretText.utf8), input: cipher) + guard let text = String(data: plain, encoding: .utf8) else { + throw cipherError("文本解码失败") + } + return text + } + + public static func createRsaBundle(keySize: Int) throws -> NativeRsaBundle { + let normalizedKeySize = try normalizeRsaKeySize(keySize) + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeySizeInBits as String: normalizedKeySize, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: false + ], + kSecPublicKeyAttrs as String: [ + kSecAttrIsPermanent as String: false + ] + ] + + var error: Unmanaged? + guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + throw securityError(error, fallback: "RSA生成失败") + } + guard let publicKey = SecKeyCopyPublicKey(privateKey) else { + throw cipherError("RSA公钥生成失败") + } + + guard let privateData = SecKeyCopyExternalRepresentation(privateKey, &error) as Data? else { + throw securityError(error, fallback: "导出私钥失败") + } + guard let publicPkcs1 = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else { + throw securityError(error, fallback: "导出公钥失败") + } + + let publicPem = pemString(header: "PUBLIC KEY", body: wrapRsaPublicKeyToSpki(publicPkcs1)) + let privatePem = pemString(header: "RSA PRIVATE KEY", body: privateData) + return NativeRsaBundle(publicKey: publicPem, privateKey: privatePem) + } + + public static func rsaEncrypt(publicKey: String, plainText: String, outputKind: String?) throws -> String { + let key = try makeRsaPublicKey(from: publicKey) + let plainData = Data(plainText.utf8) + let encrypted = try rsaChunkedTransform(key: key, data: plainData, encrypting: true) + return try encodeCipherOutput(encrypted, kind: outputKind) + } + + public static func rsaDecrypt(privateKey: String, cipherText: String, outputKind: String?) throws -> String { + let key = try makeRsaPrivateKey(from: privateKey) + let cipherData = try decodeCipherInput(cipherText, kind: outputKind) + let decrypted = try rsaChunkedTransform(key: key, data: cipherData, encrypting: false) + guard let text = String(data: decrypted, encoding: .utf8) else { + throw cipherError("文本解码失败") + } + return text + } +} + +private enum NativeAesMode { + case ecb + case cbc + case cfb + case ctr + case ctrGladman + case ofb + + var ccMode: CCMode { + switch self { + case .ecb: + return CCMode(kCCModeECB) + case .cbc: + return CCMode(kCCModeCBC) + case .cfb: + return CCMode(kCCModeCFB) + case .ctr, .ctrGladman: + return CCMode(kCCModeCTR) + case .ofb: + return CCMode(kCCModeOFB) + } + } + + var ccOptions: CCModeOptions { + switch self { + case .ctr: + return CCModeOptions(kCCModeOptionCTR_BE) + case .ctrGladman: + // CommonCrypto 未单独提供 Gladman 计数器模式,这里映射为标准 CTR 兼容实现。 + return CCModeOptions(kCCModeOptionCTR_BE) + default: + return CCModeOptions(0) + } + } + + var needsBlockAlignmentWithoutPadding: Bool { + switch self { + case .ecb, .cbc: + return true + default: + return false + } + } + + var usesIV: Bool { + switch self { + case .ecb: + return false + default: + return true + } + } +} + +private enum NativePadding { + case pkcs7 + case ansiX923 + case iso10126 + case iso97971 + case none + case zero +} + +private struct AesConfig { + let mode: NativeAesMode + let padding: NativePadding + let keyLength: Int +} + +private struct DesConfig { + let mode: NativeAesMode + let padding: NativePadding +} + +private func makeAesConfig(mode: String, padding: String, keyLength: Int?) throws -> AesConfig { + let aesMode: NativeAesMode + switch normalize(mode) { + case "ECB": + aesMode = .ecb + case "CBC": + aesMode = .cbc + case "CFB": + aesMode = .cfb + case "CTR": + aesMode = .ctr + case "CTRGLADMAN": + aesMode = .ctrGladman + case "OFB": + aesMode = .ofb + default: + throw cipherError("不支持的AES模式") + } + + let aesPadding: NativePadding + switch normalize(padding) { + case "PKCS7": + aesPadding = .pkcs7 + case "ANSI_X923": + aesPadding = .ansiX923 + case "ISO_10126": + aesPadding = .iso10126 + case "ISO_97971": + aesPadding = .iso97971 + case "NONE": + aesPadding = .none + case "ZERO": + aesPadding = .zero + default: + throw cipherError("不支持的AES填充") + } + + return AesConfig(mode: aesMode, padding: aesPadding, keyLength: try normalizeAesKeyLength(keyLength)) +} + +private func makeDesConfig(mode: String, padding: String) throws -> DesConfig { + let desMode: NativeAesMode + switch normalize(mode) { + case "ECB": + desMode = .ecb + case "CBC": + desMode = .cbc + case "CFB": + desMode = .cfb + case "CTR", "CTRGLADMAN": + desMode = .ctr + case "OFB": + desMode = .ofb + default: + throw cipherError("不支持的DES模式") + } + + let desPadding: NativePadding + switch normalize(padding) { + case "PKCS7": + desPadding = .pkcs7 + case "ANSI_X923": + desPadding = .ansiX923 + case "ISO_10126": + desPadding = .iso10126 + case "ISO_97971": + desPadding = .iso97971 + case "NONE": + desPadding = .none + case "ZERO": + desPadding = .zero + default: + throw cipherError("不支持的DES填充") + } + + return DesConfig(mode: desMode, padding: desPadding) +} + +private func normalize(_ value: String) -> String { + value.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() +} + +private func normalizeAesKeyLength(_ keyLength: Int?) throws -> Int { + guard let keyLength else { + return kCCKeySizeAES256 + } + switch keyLength { + case 16, 24, 32: + return keyLength + case 128, 192, 256: + return keyLength / 8 + default: + throw cipherError("AES密钥长度无效") + } +} + +private func normalizeRsaKeySize(_ keySize: Int) throws -> Int { + if keySize == 1024 || keySize == 2048 || keySize == 3072 || keySize == 4096 { + return keySize + } + throw cipherError("RSA密钥长度无效") +} + +private func normalizeKey(_ secretBytes: [UInt8], to length: Int) throws -> Data { + if secretBytes.isEmpty { + throw cipherError("密钥不能为空") + } + var key = Data(secretBytes) + if key.count > length { + key = key.prefix(length) + } else if key.count < length { + key.append(Data(repeating: 0, count: length - key.count)) + } + return key +} + +private func normalizeIV(_ ivBytes: [UInt8]?, blockSize: Int, mode: NativeAesMode) throws -> Data? { + guard mode.usesIV else { + return nil + } + var iv = Data(ivBytes ?? []) + if iv.count > blockSize { + iv = iv.prefix(blockSize) + } else if iv.count < blockSize { + iv.append(Data(repeating: 0, count: blockSize - iv.count)) + } + return iv +} + +private func applyPaddingIfNeeded(_ data: Data, blockSize: Int, padding: NativePadding, mode: NativeAesMode) throws -> Data { + if padding == .none && !mode.needsBlockAlignmentWithoutPadding { + return data + } + return try applyPadding(data, blockSize: blockSize, padding: padding) +} + +private func removePaddingIfNeeded(_ data: Data, blockSize: Int, padding: NativePadding, mode: NativeAesMode) throws -> Data { + if padding == .none && !mode.needsBlockAlignmentWithoutPadding { + return data + } + return try removePadding(data, blockSize: blockSize, padding: padding) +} + +private func applyPadding(_ data: Data, blockSize: Int, padding: NativePadding) throws -> Data { + switch padding { + case .none: + if data.count % blockSize != 0 { + throw cipherError("数据长度无效") + } + return data + case .pkcs7: + let remain = data.count % blockSize + let actualPad = remain == 0 ? blockSize : blockSize - remain + return data + Data(repeating: UInt8(actualPad), count: actualPad) + case .ansiX923: + let padCount = blockSize - (data.count % blockSize) + let actualPad = padCount == 0 ? blockSize : padCount + var result = data + if actualPad > 1 { + result.append(Data(repeating: 0, count: actualPad - 1)) + } + result.append(UInt8(actualPad)) + return result + case .iso10126: + let padCount = blockSize - (data.count % blockSize) + let actualPad = padCount == 0 ? blockSize : padCount + var result = data + if actualPad > 1 { + var random = Data(count: actualPad - 1) + let status = random.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, actualPad - 1, $0.baseAddress!) } + if status != errSecSuccess { + throw cipherError("随机填充失败") + } + result.append(random) + } + result.append(UInt8(actualPad)) + return result + case .iso97971: + var result = data + result.append(0x80) + let remain = result.count % blockSize + if remain != 0 { + result.append(Data(repeating: 0, count: blockSize - remain)) + } + return result + case .zero: + let remain = data.count % blockSize + if remain == 0 { + return data + } + return data + Data(repeating: 0, count: blockSize - remain) + } +} + +private func removePadding(_ data: Data, blockSize: Int, padding: NativePadding) throws -> Data { + switch padding { + case .none: + if data.count % blockSize != 0 { + throw cipherError("数据长度无效") + } + return data + case .pkcs7: + guard let last = data.last else { return data } + let pad = Int(last) + guard pad > 0, pad <= blockSize, pad <= data.count else { + throw cipherError("填充无效") + } + let tail = data.suffix(pad) + if tail.allSatisfy({ $0 == last }) { + return Data(data.dropLast(pad)) + } + throw cipherError("填充无效") + case .ansiX923: + guard let last = data.last else { return data } + let pad = Int(last) + guard pad > 0, pad <= blockSize, pad <= data.count else { + throw cipherError("填充无效") + } + if pad > 1 && !data.dropLast().suffix(pad - 1).allSatisfy({ $0 == 0 }) { + throw cipherError("填充无效") + } + return Data(data.dropLast(pad)) + case .iso10126: + guard let last = data.last else { return data } + let pad = Int(last) + guard pad > 0, pad <= blockSize, pad <= data.count else { + throw cipherError("填充无效") + } + return Data(data.dropLast(pad)) + case .iso97971: + var index = data.endIndex + while index > data.startIndex { + index = data.index(before: index) + let value = data[index] + if value == 0x80 { + return Data(data[.. Data { + var cryptor: CCCryptorRef? + let status = key.withUnsafeBytes { keyBuffer in + ivDataPointer(iv) { ivPointer in + CCCryptorCreateWithMode( + operation, + mode, + algorithm, + CCPadding(ccNoPadding), + ivPointer, + keyBuffer.baseAddress, + key.count, + nil, + 0, + 0, + options, + &cryptor + ) + } + } + + guard status == kCCSuccess, let cryptor else { + throw cipherError("加解密初始化失败") + } + defer { CCCryptorRelease(cryptor) } + + var output = Data(count: input.count + kCCBlockSizeAES128) + var moved = 0 + var finalMoved = 0 + let updateStatus = output.withUnsafeMutableBytes { outputBuffer in + input.withUnsafeBytes { inputBuffer in + CCCryptorUpdate( + cryptor, + inputBuffer.baseAddress, + input.count, + outputBuffer.baseAddress, + output.count, + &moved + ) + } + } + guard updateStatus == kCCSuccess else { + throw cipherError("加解密失败") + } + + let finalStatus = output.withUnsafeMutableBytes { outputBuffer in + CCCryptorFinal( + cryptor, + outputBuffer.baseAddress?.advanced(by: moved), + output.count - moved, + &finalMoved + ) + } + guard finalStatus == kCCSuccess else { + throw cipherError("加解密失败") + } + output.removeSubrange((moved + finalMoved)..(_ iv: Data?, _ body: (UnsafeRawPointer?) -> T) -> T { + guard let iv else { + return body(nil) + } + return iv.withUnsafeBytes { body($0.baseAddress) } +} + +private func tripleDesTransform(operation: CCOperation, secretBytes: [UInt8], input: Data, modeName: String, ivBytes: [UInt8]?) throws -> Data { + let mode: NativeAesMode + switch normalize(modeName) { + case "ECB": + mode = .ecb + case "CBC": + mode = .cbc + default: + throw cipherError("不支持的3DES模式") + } + let key = try normalizeKey(secretBytes, to: kCCKeySize3DES) + let iv = try normalizeIV(ivBytes, blockSize: kCCBlockSize3DES, mode: mode) + return try cryptWithMode( + operation: operation, + algorithm: CCAlgorithm(kCCAlgorithm3DES), + mode: mode.ccMode, + options: CCModeOptions(0), + key: key, + iv: iv, + input: input + ) +} + +private func rc4Transform(secret: Data, input: Data) throws -> Data { + if secret.isEmpty { + throw cipherError("密钥不能为空") + } + var output = Data(count: input.count + kCCBlockSizeRC2) + var moved = 0 + let status = output.withUnsafeMutableBytes { outputBuffer in + secret.withUnsafeBytes { keyBuffer in + input.withUnsafeBytes { inputBuffer in + CCCrypt( + CCOperation(kCCEncrypt), + CCAlgorithm(kCCAlgorithmRC4), + CCOptions(0), + keyBuffer.baseAddress, + secret.count, + nil, + inputBuffer.baseAddress, + input.count, + outputBuffer.baseAddress, + output.count, + &moved + ) + } + } + } + guard status == kCCSuccess else { + throw cipherError("RC4处理失败") + } + output.removeSubrange(moved.. Void) -> Data { + var output = Data(count: length) + output.withUnsafeMutableBytes { outputBuffer in + input.withUnsafeBytes { inputBuffer in + body(outputBuffer, inputBuffer.baseAddress, input.count) + } + } + return output +} + +private func utf8String(from data: Data) throws -> String { + guard let text = String(data: data, encoding: .utf8) else { + throw cipherError("文本解码失败") + } + return text +} + +private func latin1String(from data: Data) -> String { + var output = "" + output.reserveCapacity(data.count) + for byte in data { + if let scalar = UnicodeScalar(Int(byte)) { + output.append(Character(scalar)) + } + } + return output +} + +private func dataFromLatin1String(_ text: String) -> Data { + var bytes = [UInt8]() + bytes.reserveCapacity(text.count) + for value in text.unicodeScalars { + bytes.append(UInt8(value.value & 0xFF)) + } + return Data(bytes) +} + +private func utf16CodecString(from data: Data) -> String { + if data.isEmpty { + return "" + } + var units = [UInt16]() + units.reserveCapacity((data.count + 1) / 2) + var index = data.startIndex + while index < data.endIndex { + let high = UInt16(data[index]) << 8 + let nextIndex = data.index(after: index) + let low: UInt16 = nextIndex < data.endIndex ? UInt16(data[nextIndex]) : 0 + units.append(high | low) + index = data.index(index, offsetBy: 2, limitedBy: data.endIndex) ?? data.endIndex + } + return String(utf16CodeUnits: units, count: units.count) +} + +private func dataFromUtf16CodecString(_ text: String) -> Data { + let units = Array(text.utf16) + var bytes = [UInt8]() + bytes.reserveCapacity(units.count * 2) + for unit in units { + bytes.append(UInt8((unit >> 8) & 0xFF)) + bytes.append(UInt8(unit & 0xFF)) + } + return Data(bytes) +} + +private func makeRsaPublicKey(from pem: String) throws -> SecKey { + let body = try decodePemBody(from: pem) + let raw = pem.contains("BEGIN PUBLIC KEY") ? try unwrapSubjectPublicKeyInfo(body) : body + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPublic, + kSecAttrKeySizeInBits as String: rsaBitLength(fromPkcs1: raw) + ] + var error: Unmanaged? + guard let key = SecKeyCreateWithData(raw as CFData, attributes as CFDictionary, &error) else { + throw securityError(error, fallback: "公钥无效") + } + return key +} + +private func makeRsaPrivateKey(from pem: String) throws -> SecKey { + let body = try decodePemBody(from: pem) + let raw = pem.contains("BEGIN PRIVATE KEY") && !pem.contains("BEGIN RSA PRIVATE KEY") ? try unwrapPkcs8PrivateKey(body) : body + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, + kSecAttrKeySizeInBits as String: rsaBitLength(fromPrivateKey: raw) + ] + var error: Unmanaged? + guard let key = SecKeyCreateWithData(raw as CFData, attributes as CFDictionary, &error) else { + throw securityError(error, fallback: "私钥无效") + } + return key +} + +private func rsaChunkedTransform(key: SecKey, data: Data, encrypting: Bool) throws -> Data { + let algorithm: SecKeyAlgorithm = .rsaEncryptionPKCS1 + guard SecKeyIsAlgorithmSupported(key, encrypting ? .encrypt : .decrypt, algorithm) else { + throw cipherError(encrypting ? "RSA加密不可用" : "RSA解密不可用") + } + + let blockSize = SecKeyGetBlockSize(key) + let chunkSize = encrypting ? blockSize - 11 : blockSize + if chunkSize <= 0 { + throw cipherError("RSA块大小无效") + } + + var result = Data() + var offset = 0 + while offset < data.count { + let next = min(offset + chunkSize, data.count) + let chunk = data.subdata(in: offset..? + let transformed: Data? + if encrypting { + transformed = SecKeyCreateEncryptedData(key, algorithm, chunk as CFData, &error) as Data? + } else { + transformed = SecKeyCreateDecryptedData(key, algorithm, chunk as CFData, &error) as Data? + } + guard let transformed else { + throw securityError(error, fallback: encrypting ? "RSA加密失败" : "RSA解密失败") + } + result.append(transformed) + offset = next + } + return result +} + +private func encodeCipherOutput(_ data: Data, kind: String?) throws -> String { + switch normalize(kind ?? "base64") { + case "BASE64": + return data.base64EncodedString() + case "HEX": + return data.hexString() + default: + throw cipherError("RSA输出格式无效") + } +} + +private func decodeCipherInput(_ text: String, kind: String?) throws -> Data { + switch normalize(kind ?? "base64") { + case "BASE64": + guard let data = Data(base64Encoded: text) else { + throw cipherError("Base64无效") + } + return data + case "HEX": + return try Data(hexString: text) + default: + throw cipherError("RSA输入格式无效") + } +} + +private func decodePemBody(from pem: String) throws -> Data { + let lines = pem + .components(separatedBy: .newlines) + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty && !$0.hasPrefix("-----BEGIN") && !$0.hasPrefix("-----END") } + let joined = lines.joined() + guard let data = Data(base64Encoded: joined) else { + throw cipherError("PEM无效") + } + return data +} + +private func pemString(header: String, body: Data) -> String { + let content = body.base64EncodedString() + let lines = stride(from: 0, to: content.count, by: 64).map { start -> String in + let startIndex = content.index(content.startIndex, offsetBy: start) + let endIndex = content.index(startIndex, offsetBy: min(64, content.count - start)) + return String(content[startIndex.. Data { + let algorithmIdentifier = Data([0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00]) + let bitString = asn1Wrap(tag: 0x03, content: Data([0x00]) + pkcs1) + return asn1Wrap(tag: 0x30, content: algorithmIdentifier + bitString) +} + +private func unwrapSubjectPublicKeyInfo(_ der: Data) throws -> Data { + let sequence = try readAsn1Element(from: der, at: 0, expectedTag: 0x30) + var cursor = sequence.contentStartIndex + let algorithm = try readAsn1Element(from: der, at: cursor, expectedTag: 0x30) + cursor = algorithm.endIndex + let bitString = try readAsn1Element(from: der, at: cursor, expectedTag: 0x03) + let bitStringData = der.subdata(in: bitString.contentStartIndex.. Data { + let sequence = try readAsn1Element(from: der, at: 0, expectedTag: 0x30) + var cursor = sequence.contentStartIndex + let version = try readAsn1Element(from: der, at: cursor, expectedTag: 0x02) + cursor = version.endIndex + let algorithm = try readAsn1Element(from: der, at: cursor, expectedTag: 0x30) + cursor = algorithm.endIndex + let octetString = try readAsn1Element(from: der, at: cursor, expectedTag: 0x04) + return der.subdata(in: octetString.contentStartIndex.. Asn1Element { + guard index < data.count, data[index] == expectedTag else { + throw cipherError("PEM无效") + } + let lengthInfo = try readAsn1Length(from: data, at: index + 1) + let contentStart = lengthInfo.nextIndex + let end = contentStart + lengthInfo.length + guard end <= data.count else { + throw cipherError("PEM无效") + } + return Asn1Element(contentStartIndex: contentStart, endIndex: end) +} + +private func readAsn1Length(from data: Data, at index: Int) throws -> (length: Int, nextIndex: Int) { + guard index < data.count else { + throw cipherError("PEM无效") + } + let first = data[index] + if first & 0x80 == 0 { + return (Int(first), index + 1) + } + let byteCount = Int(first & 0x7F) + guard byteCount > 0, index + byteCount < data.count else { + throw cipherError("PEM无效") + } + var length = 0 + for offset in 0.. Data { + var output = Data([tag]) + output.append(asn1LengthBytes(content.count)) + output.append(content) + return output +} + +private func asn1LengthBytes(_ length: Int) -> Data { + if length < 0x80 { + return Data([UInt8(length)]) + } + var value = length + var bytes: [UInt8] = [] + while value > 0 { + bytes.insert(UInt8(value & 0xFF), at: 0) + value >>= 8 + } + return Data([0x80 | UInt8(bytes.count)] + bytes) +} + +private func rsaBitLength(fromPkcs1 data: Data) -> Int { + if let modulus = try? extractRsaModulus(fromPublicPkcs1: data) { + return modulusBitLength(modulus) + } + return max(1024, data.count * 8) +} + +private func rsaBitLength(fromPrivateKey data: Data) -> Int { + if let modulus = try? extractRsaModulus(fromPrivatePkcs1: data) { + return modulusBitLength(modulus) + } + return max(1024, data.count * 8) +} + +private func extractRsaModulus(fromPublicPkcs1 data: Data) throws -> Data { + let sequence = try readAsn1Element(from: data, at: 0, expectedTag: 0x30) + let integer = try readAsn1Element(from: data, at: sequence.contentStartIndex, expectedTag: 0x02) + return trimmedInteger(data.subdata(in: integer.contentStartIndex.. Data { + let sequence = try readAsn1Element(from: data, at: 0, expectedTag: 0x30) + var cursor = sequence.contentStartIndex + let version = try readAsn1Element(from: data, at: cursor, expectedTag: 0x02) + cursor = version.endIndex + let modulus = try readAsn1Element(from: data, at: cursor, expectedTag: 0x02) + return trimmedInteger(data.subdata(in: modulus.contentStartIndex.. Data { + var result = data + while result.count > 1 && result.first == 0x00 { + result.removeFirst() + } + return result +} + +private func modulusBitLength(_ modulus: Data) -> Int { + guard let first = modulus.first else { + return 0 + } + var bits = modulus.count * 8 + var mask: UInt8 = 0x80 + while mask > 0, first & mask == 0 { + bits -= 1 + mask >>= 1 + } + return bits +} + +private func cipherError(_ message: String) -> NSError { + NSError(domain: "NativeCipherCore", code: -1, userInfo: [NSLocalizedDescriptionKey: message]) +} + +private func securityError(_ error: Unmanaged?, fallback: String) -> NSError { + if let error { + let message = CFErrorCopyDescription(error.takeRetainedValue()) as String + return cipherError(message.isEmpty ? fallback : message) + } + return cipherError(fallback) +} + +private extension Data { + init(hexString: String) throws { + let cleaned = hexString.trimmingCharacters(in: .whitespacesAndNewlines) + guard cleaned.count % 2 == 0 else { + throw cipherError("Hex无效") + } + var output = Data(capacity: cleaned.count / 2) + var index = cleaned.startIndex + while index < cleaned.endIndex { + let next = cleaned.index(index, offsetBy: 2) + let pair = cleaned[index.. String { + map { String(format: "%02x", $0) }.joined() + } +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/app-ios/index.uts b/uni_modules/laoqianjunzi-crypto/utssdk/app-ios/index.uts new file mode 100644 index 0000000..8cb339d --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/app-ios/index.uts @@ -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()) +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/deps/package-lock.json b/uni_modules/laoqianjunzi-crypto/utssdk/deps/package-lock.json new file mode 100644 index 0000000..cc788f8 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/deps/package-lock.json @@ -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" + } + } +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/deps/package.json b/uni_modules/laoqianjunzi-crypto/utssdk/deps/package.json new file mode 100644 index 0000000..44e63ee --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/deps/package.json @@ -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" + } +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/index.uts b/uni_modules/laoqianjunzi-crypto/utssdk/index.uts new file mode 100644 index 0000000..96b9734 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/index.uts @@ -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 diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/interface.uts b/uni_modules/laoqianjunzi-crypto/utssdk/interface.uts new file mode 100644 index 0000000..b802f32 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/interface.uts @@ -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 +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/mp-weixin/index.uts b/uni_modules/laoqianjunzi-crypto/utssdk/mp-weixin/index.uts new file mode 100644 index 0000000..08abc37 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/mp-weixin/index.uts @@ -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()) +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/shared/compat.uts b/uni_modules/laoqianjunzi-crypto/utssdk/shared/compat.uts new file mode 100644 index 0000000..fdb4068 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/shared/compat.uts @@ -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) +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/shared/options.uts b/uni_modules/laoqianjunzi-crypto/utssdk/shared/options.uts new file mode 100644 index 0000000..380143e --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/shared/options.uts @@ -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 +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/shared/portable_crypto.ts b/uni_modules/laoqianjunzi-crypto/utssdk/shared/portable_crypto.ts new file mode 100644 index 0000000..3515bbe --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/shared/portable_crypto.ts @@ -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) +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/unierror.uts b/uni_modules/laoqianjunzi-crypto/utssdk/unierror.uts new file mode 100644 index 0000000..a59d331 --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/unierror.uts @@ -0,0 +1,27 @@ +import { CryptoBridgeErrorCode, CryptoBridgeFailure } from "./interface.uts" + +export const CryptoErrorSubject = "laoqianjunzi-crypto" + +export const CryptoErrorMessages : Map = 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] ?? "" + } +} diff --git a/uni_modules/laoqianjunzi-crypto/utssdk/web/index.uts b/uni_modules/laoqianjunzi-crypto/utssdk/web/index.uts new file mode 100644 index 0000000..3ac065e --- /dev/null +++ b/uni_modules/laoqianjunzi-crypto/utssdk/web/index.uts @@ -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()) +}