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) }