@liujitcn/kratos-uni-app-core 0.0.32 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/utils/passwordCrypto.ts +40 -30
package/package.json
CHANGED
|
@@ -2,10 +2,7 @@ import { defLoginService } from '../api/base/v1/login'
|
|
|
2
2
|
import { t } from '../locales'
|
|
3
3
|
import { PasswordCryptoScene } from '../rpc/base/v1/login'
|
|
4
4
|
import type { PasswordCrypto } from '../rpc/common/v1/types'
|
|
5
|
-
|
|
6
|
-
// #ifdef MP-WEIXIN
|
|
7
5
|
import * as miniCrypto from 'asmcrypto.js'
|
|
8
|
-
// #endif
|
|
9
6
|
|
|
10
7
|
type MiniCrypto = typeof import('asmcrypto.js')
|
|
11
8
|
|
|
@@ -53,8 +50,12 @@ function getSubtleCrypto() {
|
|
|
53
50
|
return cryptoApi
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
/**
|
|
57
|
-
function
|
|
53
|
+
/** 生成纯 JavaScript 密码加密所需的随机字节。 */
|
|
54
|
+
function getPortableRandomBytes(length: number) {
|
|
55
|
+
const cryptoApi = globalThis.crypto
|
|
56
|
+
if (cryptoApi?.getRandomValues) {
|
|
57
|
+
return Promise.resolve(cryptoApi.getRandomValues(new Uint8Array(length)).buffer)
|
|
58
|
+
}
|
|
58
59
|
return new Promise<ArrayBuffer>((resolve, reject) => {
|
|
59
60
|
if (typeof wx === 'undefined' || typeof wx.getRandomValues !== 'function') {
|
|
60
61
|
reject(new Error(t('core.crypto.unsupported')))
|
|
@@ -119,12 +120,16 @@ function trimDerInteger(value: Uint8Array) {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
/** 从 PEM SubjectPublicKeyInfo 中提取 RSA 模数和指数。 */
|
|
122
|
-
function
|
|
123
|
+
function parsePortableRsaPublicKey(publicKey: string): [Uint8Array, Uint8Array] {
|
|
123
124
|
const base64 = publicKey
|
|
124
125
|
.replace(/-----BEGIN PUBLIC KEY-----/g, '')
|
|
125
126
|
.replace(/-----END PUBLIC KEY-----/g, '')
|
|
126
127
|
.replace(/\s/g, '')
|
|
127
|
-
const der = new Uint8Array(
|
|
128
|
+
const der = new Uint8Array(
|
|
129
|
+
typeof wx !== 'undefined' && typeof wx.base64ToArrayBuffer === 'function'
|
|
130
|
+
? wx.base64ToArrayBuffer(base64)
|
|
131
|
+
: pemToArrayBuffer(publicKey),
|
|
132
|
+
)
|
|
128
133
|
const subjectPublicKeyInfo = readDerNode(der, 0)
|
|
129
134
|
const algorithm = readDerNode(subjectPublicKeyInfo.value, 0)
|
|
130
135
|
const bitString = readDerNode(subjectPublicKeyInfo.value, algorithm.next)
|
|
@@ -164,13 +169,17 @@ function generateMgf1Mask(crypto: MiniCrypto, seed: Uint8Array, length: number)
|
|
|
164
169
|
return mask
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
/**
|
|
168
|
-
function
|
|
169
|
-
|
|
172
|
+
/** 将字节数组编码为当前运行端可提交的 Base64。 */
|
|
173
|
+
function portableBytesToBase64(value: Uint8Array) {
|
|
174
|
+
const buffer = value.slice().buffer
|
|
175
|
+
if (typeof wx !== 'undefined' && typeof wx.arrayBufferToBase64 === 'function') {
|
|
176
|
+
return wx.arrayBufferToBase64(buffer)
|
|
177
|
+
}
|
|
178
|
+
return arrayBufferToBase64(buffer)
|
|
170
179
|
}
|
|
171
180
|
|
|
172
|
-
/** 使用纯 JavaScript
|
|
173
|
-
async function
|
|
181
|
+
/** 使用纯 JavaScript 实现兼容缺少 WebCrypto 的密码加密协议。 */
|
|
182
|
+
async function encryptPortablePassword(
|
|
174
183
|
crypto: MiniCrypto,
|
|
175
184
|
password: string,
|
|
176
185
|
publicKeyResponse: {
|
|
@@ -180,13 +189,13 @@ async function encryptMiniProgramPassword(
|
|
|
180
189
|
nonce: string
|
|
181
190
|
},
|
|
182
191
|
): Promise<PasswordCrypto> {
|
|
183
|
-
const aesKey = new Uint8Array(await
|
|
184
|
-
const iv = new Uint8Array(await
|
|
192
|
+
const aesKey = new Uint8Array(await getPortableRandomBytes(32))
|
|
193
|
+
const iv = new Uint8Array(await getPortableRandomBytes(12))
|
|
185
194
|
const plaintext = crypto.string_to_bytes(password, true)
|
|
186
|
-
const [modulus, exponent] =
|
|
195
|
+
const [modulus, exponent] = parsePortableRsaPublicKey(publicKeyResponse.public_key)
|
|
187
196
|
const hash = new crypto.Sha256()
|
|
188
197
|
const keySize = Math.ceil(new crypto.BigNumber(modulus).bitLength / 8)
|
|
189
|
-
const seed = new Uint8Array(await
|
|
198
|
+
const seed = new Uint8Array(await getPortableRandomBytes(hash.HASH_SIZE))
|
|
190
199
|
const dataBlockLength = keySize - hash.HASH_SIZE - 1
|
|
191
200
|
const paddingLength = dataBlockLength - aesKey.length - hash.HASH_SIZE - 1
|
|
192
201
|
if (paddingLength < 0) {
|
|
@@ -222,9 +231,9 @@ async function encryptMiniProgramPassword(
|
|
|
222
231
|
key_id: publicKeyResponse.key_id,
|
|
223
232
|
nonce: publicKeyResponse.nonce,
|
|
224
233
|
algorithm: publicKeyResponse.algorithm,
|
|
225
|
-
encrypted_key:
|
|
226
|
-
iv:
|
|
227
|
-
ciphertext:
|
|
234
|
+
encrypted_key: portableBytesToBase64(encryptedKey),
|
|
235
|
+
iv: portableBytesToBase64(iv),
|
|
236
|
+
ciphertext: portableBytesToBase64(ciphertext),
|
|
228
237
|
}
|
|
229
238
|
}
|
|
230
239
|
|
|
@@ -240,30 +249,31 @@ export async function encryptPassword(
|
|
|
240
249
|
|
|
241
250
|
const publicKeyResponse = await defLoginService.PasswordPublicKey({ scene })
|
|
242
251
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
252
|
+
const cryptoApi = globalThis.crypto
|
|
253
|
+
const isSecureContext = typeof window === 'undefined' || window.isSecureContext
|
|
254
|
+
if (!cryptoApi?.subtle || !isSecureContext) {
|
|
255
|
+
return encryptPortablePassword(miniCrypto, plainPassword, publicKeyResponse)
|
|
256
|
+
}
|
|
247
257
|
|
|
248
|
-
const
|
|
249
|
-
const publicKey = await
|
|
258
|
+
const subtleCrypto = getSubtleCrypto()
|
|
259
|
+
const publicKey = await subtleCrypto.subtle.importKey(
|
|
250
260
|
'spki',
|
|
251
261
|
pemToArrayBuffer(publicKeyResponse.public_key),
|
|
252
262
|
{ name: 'RSA-OAEP', hash: 'SHA-256' },
|
|
253
263
|
false,
|
|
254
264
|
['encrypt'],
|
|
255
265
|
)
|
|
256
|
-
const aesKey = await
|
|
266
|
+
const aesKey = await subtleCrypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, [
|
|
257
267
|
'encrypt',
|
|
258
268
|
])
|
|
259
|
-
const rawAesKey = await
|
|
260
|
-
const iv =
|
|
261
|
-
const ciphertext = await
|
|
269
|
+
const rawAesKey = await subtleCrypto.subtle.exportKey('raw', aesKey)
|
|
270
|
+
const iv = subtleCrypto.getRandomValues(new Uint8Array(12))
|
|
271
|
+
const ciphertext = await subtleCrypto.subtle.encrypt(
|
|
262
272
|
{ name: 'AES-GCM', iv },
|
|
263
273
|
aesKey,
|
|
264
274
|
new TextEncoder().encode(plainPassword),
|
|
265
275
|
)
|
|
266
|
-
const encryptedKey = await
|
|
276
|
+
const encryptedKey = await subtleCrypto.subtle.encrypt({ name: 'RSA-OAEP' }, publicKey, rawAesKey)
|
|
267
277
|
|
|
268
278
|
return {
|
|
269
279
|
key_id: publicKeyResponse.key_id,
|