diff --git a/OAT.xml b/OAT.xml index 4471a41dc398916305a03ae6b29a3c6d5ae185fe..fb2b2507c7e18d6f832870e95c5c39323ce4b8a2 100644 --- a/OAT.xml +++ b/OAT.xml @@ -179,10 +179,10 @@ Note:If the text contains special characters, please escape them according to th - - - - + + + + @@ -201,6 +201,8 @@ Note:If the text contains special characters, please escape them according to th + + @@ -448,10 +450,10 @@ Note:If the text contains special characters, please escape them according to th - - - - + + + + diff --git a/code/BasicFeature/Security/KeyManager/.gitignore b/code/BasicFeature/Security/KeyManager/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/AppScope/app.json5 b/code/BasicFeature/Security/KeyManager/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..f9b5a8318e55ac131f19e817dd06b041310d6f69 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/AppScope/app.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "app": { + "bundleName": "com.samples.keymanager", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/code/BasicFeature/Security/KeyManager/AppScope/resources/base/element/string.json b/code/BasicFeature/Security/KeyManager/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..33b7ce7028ebb625a58c756d9579651751e0d296 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "KeyManager" + } + ] +} diff --git a/code/BasicFeature/Security/KeyManager/AppScope/resources/base/media/background.png b/code/BasicFeature/Security/KeyManager/AppScope/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/code/BasicFeature/Security/KeyManager/AppScope/resources/base/media/background.png differ diff --git a/code/BasicFeature/Security/KeyManager/AppScope/resources/base/media/foreground.png b/code/BasicFeature/Security/KeyManager/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/code/BasicFeature/Security/KeyManager/AppScope/resources/base/media/foreground.png differ diff --git a/code/BasicFeature/Security/KeyManager/AppScope/resources/base/media/layered_image.json b/code/BasicFeature/Security/KeyManager/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/AppScope/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/README.md b/code/BasicFeature/Security/KeyManager/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0ce012c6605ba279234f248c43ba83eb3698109f --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/README.md @@ -0,0 +1,346 @@ +# 通用密钥管理 + +### 介绍 + +本示例介绍了如何使用AES和RSA进行数据加解密,并模拟了服务端和客户端的加解密通信流程。 + +### 效果图预览 + + + +**使用说明**: + +1. 点击输入框,输入任何内容,点击“发送”按钮,页面将会展示客户端和服务端的加解密通信过程。 +2. 客户端会在2s后调用模拟服务端的方法,页面将会展示签名验证数据的过程。 + +### 实现思路 + +1. 创建页面,添加List用于显示加解密流程信息,添加TextInput用于输入数据。详细代码可参考[Index.ets](./entry/src/main/ets/pages/KeyManager.ets)。 + + ```typescript + @Entry + @Component + struct KeyManager { + ... + + build() { + RelativeContainer() { + Row() { + TextInput({ text: this.message, placeholder: '请输入内容' }) + .id('input') + .onChange((value: string) => { + this.message = value; + }) + .layoutWeight(4) + ... + } + ... + + List() { + ForEach(this.messageArray, (message: Message) => { + ListItem() { + Row() { + Text(message.message) + .padding(10) + .fontColor(message.position === MessagePosition.Left ? Color.Blue : Color.Black) + .constraintSize({ + maxWidth: '80%' + }) + .borderRadius(10) + .backgroundColor("#F1F1F1") + .margin({ + left: message.position === MessagePosition.Left ? 10 : 0, + right: message.position === MessagePosition.Right ? 10 : 0 + }) + } + ... + } + }) + } + ... + } + + } + ``` + +2. 分别实现AES密钥生成方法和加解密方法。详细代码可参考[AesUtils.ets](./entry/src/main/ets/utils/AesUtils.ets)。 + + ```typescript + /** + * 生成AES密钥 + * @returns AES密钥的16进制字符串 + */ + export async function generateAesKey(): Promise { + ... + } + + /** + * AES解密 + * @param encryptedMessage 已经加密的数据 + * @param aesKey AES密钥 + * @returns 解密后的数据 + */ + export async function aesGcmDecrypt(encryptedMessage: string, aesKey: string): Promise { + ... + } + + /** + * AES加密 + * @param textString 需要加密的数据 + * @param aesKey AES密钥 + * @returns + */ + export async function aesGcmEncrypt(textString: string, aesKey: string): Promise { + ... + } + ``` + +3. 分别实现RSA密钥生成方法、加解密方法、签名验证方法。详细代码可参考[RsaUtils.ets](./entry/src/main/ets/utils/RsaUtils.ets)。 + + ```typescript + /** + * 生成RSA密钥对 + * @returns RSA密钥对 + */ + export async function generateRsaKey(): Promise { + ... + } + + /** + * RSA加密 + * @param data 需要加密的数据 + * @param publicKey 公钥 + * @returns 完成加密的数据 + */ + export async function rsaEncryption(data: string, publicKey: string): Promise { + ... + } + + /** + * RSA解密 + * @param encryptedData 经过加密的数据 + * @param privateKey 私钥 + * @returns 解密后的数据 + */ + export async function rsaDecryption(encryptedData: string, privateKey: string): Promise { + ... + return ""; + } + + /** + * 验证签名 + * @param encryptedData 待验证的数据 + * @param singedData 签名信息 + * @param publicKey 公钥 + * @returns 签名验证是否通过 + */ + export async function verify(encryptedData: string, singedData: string, publicKey: string): Promise { + ... + } + + /** + * 签名 + * @param data 需要签名的数据 + * @param privateKey 私钥 + * @returns 签名信息 + */ + export async function sign(data: string, privateKey: string): Promise { + ... + } + ``` + +4. 在客户端中进行AES密钥生成、加解密以及RSA加密。详细代码可参考[LocalClient.ets](./entry/src/main/ets/client/LocalClient.ets)。 + + ```typescript + // 客户端下载公钥 + async downloadPublicKey() { + let publicKeyResult: SignedData = JSON.parse(await this.server.downloadPublicKey()); + // 验证公钥签名 + if (await verify(publicKeyResult.encryptedMessage, publicKeyResult.signedMessage, + publicKeyResult.encryptedMessage)) { + this.publicKey = publicKeyResult.encryptedMessage; + } + if (this.publicKey === "") { + sendProcessMessage("获取公钥失败", MessagePosition.Right); + throw new Error('downloadPublicKey failed'); + } + sendProcessMessage("获取公钥成功", MessagePosition.Right); + } + + // 发送数据到服务端 + async sendMessageToServer(message: string): Promise { + sendProcessMessage("开始发送数据到服务端...", MessagePosition.Right); + // 消息加密 + let encryptionMessage: string = await this.encryption(message); + sendProcessMessage("发送加密数据到服务端...", MessagePosition.Right); + // 发送服务端 + await this.server.receiveMessageFromClient(encryptionMessage); + } + + // 从服务端接收数据 + async receiveMessageFromServer(): Promise { + sendProcessMessage("开始接收服务端数据...", MessagePosition.Right); + let signMessage: string = await this.server.sendMessageToClient(); + // 验证签名 + let signData: SignedData = JSON.parse(signMessage); + sendProcessMessage("开始验证签名...", MessagePosition.Right); + let isVerified: boolean = await verify(signData.encryptedMessage, signData.signedMessage, this.publicKey); + if (isVerified) { + sendProcessMessage("签名验证成功,开始解密...", MessagePosition.Right); + let decryptedMessage = await aesGcmDecrypt(signData.encryptedMessage, this.aesKey); + sendProcessMessage("解密成功,解密结果:" + decryptedMessage, MessagePosition.Right); + return decryptedMessage; + } + sendProcessMessage("签名验证失败", MessagePosition.Right); + return "签名验证失败"; + } + + // 加密 + async encryption(message: string): Promise { + if (!this.publicKey) { + sendProcessMessage("本地未找到公钥,开始下载...", MessagePosition.Right); + // 获取公钥 + await this.downloadPublicKey(); + sendProcessMessage("公钥下载成功", MessagePosition.Right); + } + if (this.aesKey === "") { + sendProcessMessage("本地未找到AES密钥,开始生成...", MessagePosition.Right); + // 生成AES密钥 + this.aesKey = await generateAesKey(); + sendProcessMessage(`AES密钥生成成功,密钥:${this.aesKey},开始发送到服务端...`, MessagePosition.Right); + // 将AES密钥发送到服务端 + await this.sendAesKeyToServer(); + } + sendProcessMessage(`客户端开始加密数据...`, MessagePosition.Right); + let encryptionResult: string = await aesGcmEncrypt(message, this.aesKey); + sendProcessMessage(`客户端加密成功,加密结果:${encryptionResult}`, MessagePosition.Right); + // 使用AES加密数据 + return encryptionResult; + } + + // 发送AES密钥到服务端 + async sendAesKeyToServer() { + sendProcessMessage(`AES密钥进行加密...`, MessagePosition.Right); + // 对AES密钥使用公钥进行加密 + let encryptedAesKey: string = await rsaEncryption(this.aesKey, this.publicKey); + sendProcessMessage(`加密成功,加密结果:${encryptedAesKey},开始发送到服务端...`, MessagePosition.Right); + // 将加密后的AES密钥发送到服务端 + await this.server.receiveKeyFromClient(encryptedAesKey); + } + ``` + +5. 在客户端中进行AES密钥生成、加解密以及RSA加密。详细代码可参考[LocalClient.ets](./entry/src/main/ets/client/LocalClient.ets)。 + + ```typescript + //生成证书 + async createKey() { + sendProcessMessage("模拟服务端开始生成证书", MessagePosition.Left); + let rsaKey: RsaKey | undefined = await generateRsaKey() + if (rsaKey) { + this.publicKey = rsaKey.publicKey; + this.privateKey = rsaKey.privateKey; + sendProcessMessage("模拟服务端生成证书成功,公钥:" + this.publicKey, MessagePosition.Left); + } + } + + // 模拟公钥下载 + async downloadPublicKey(): Promise { + if (this.publicKey === "") { + // 创建密钥 + await this.createKey(); + } + // 对公钥进行签名 + let signResult: string = await sign(this.publicKey, this.privateKey); + let publicKeyResult: SignedData = { encryptedMessage: this.publicKey, signedMessage: signResult }; + return JSON.stringify(publicKeyResult); + } + + async receiveKeyFromClient(encryptedAesKey: string) { + sendProcessMessage(`模拟服务端接收到AES密钥:${encryptedAesKey}`, MessagePosition.Left); + this.aesKey = await rsaDecryption(encryptedAesKey, this.privateKey); + sendProcessMessage(`模拟服务端AES密钥解密成功,AES密钥为:${this.aesKey}`, MessagePosition.Left); + Logger.info(TAG, 'receive key from client success, server aesKey:' + this.aesKey); + } + + // 获取客户端消息 + async receiveMessageFromClient(message: string): Promise { + sendProcessMessage("模拟服务端接收到数据,开始解密...", MessagePosition.Left); + // 解密 + let decryptedMessage = await aesGcmDecrypt(message, this.aesKey); + sendProcessMessage(`模拟服务端解密成功,解密结果:${decryptedMessage}`, MessagePosition.Left); + // 存储 + this.messageStorage.push(decryptedMessage); + } + + async sendMessageToClient(): Promise { + let needSendMessage: string = "这是模拟服务端返回的测试数据," + this.messageStorage[this.messageStorage.length-1]; + sendProcessMessage(`模拟服务端开始发送数据,将要发送的数据是:${needSendMessage}`, MessagePosition.Left); + sendProcessMessage(`模拟服务端开始加密数据:${needSendMessage}`, MessagePosition.Left); + let encryptedMessage: string = await aesGcmEncrypt(needSendMessage, this.aesKey); + sendProcessMessage(`模拟服务端加密成功,加密结果:${encryptedMessage}`, MessagePosition.Left); + // 签名 + sendProcessMessage(`模拟服务端开始签名数据...`, MessagePosition.Left); + let signedMessage: string = await sign(encryptedMessage, this.privateKey); + sendProcessMessage(`模拟服务端签名成功,签名结果:${signedMessage}`, MessagePosition.Left); + let signData: SignedData = { encryptedMessage: encryptedMessage, signedMessage: signedMessage }; + // 发送消息 + return JSON.stringify(signData); + } + ``` + +### 工程结构&模块类型 + +``` +KeyManger // har类型 +|---client +| |---LocalClient.ets // 模拟客户端 +|---pages +| |---KeyManager.ets // 应用页面 +|---server +| |---LocalMockServer.ets // 模拟服务端 +|---utils +| |---AesUtils.ets // AES加密工具类 +| |---Logger.ets // 日志打印类 +| |---RsaUtils.ets // RSA加密工具类 +| |---Utils.ets // 其他公用方法 +``` + +### 模块依赖 + +无 + +### 相关权限 + +无 + +### 约束与限制 + +1. 本示例仅支持标准系统上运行。 + +2. 本示例为Stage模型,从API version 12开始支持。SDK版本号:5.0.0.71 Release,镜像版本号:OpenHarmony 5.0.1.107。 + +3. 本示例需要使用DevEco Studio 5.0.4 Release (Build Version: 5.0.11.100, built on March 28, 2025)编译运行。 + +### 下载 + +如需单独下载本工程,执行如下命令: + +```shell +git init +git config core.sparsecheckout true +echo code/BasicFeature/Security/KeyManager/ > .git/info/sparse-checkout +git remote add origin https://gitee.com/openharmony/applications_app_samples.git +git pull origin master +``` + +### 参考资料 + +[加解密算法库框架cryptoFramework](https://docs.openharmony.cn/pages/v5.1/zh-cn/application-dev/reference/apis-crypto-architecture-kit/js-apis-cryptoFramework.md#cryptoframeworkcreatesymkeygenerator) + +[使用RSA非对称密钥(PKCS1模式)加解密](https://docs.openharmony.cn/pages/v5.1/zh-cn/application-dev/security/CryptoArchitectureKit/crypto-rsa-asym-encrypt-decrypt-pkcs1.md) + +[使用AES对称密钥(GCM模式)加解密](https://docs.openharmony.cn/pages/v5.1/zh-cn/application-dev/security/CryptoArchitectureKit/crypto-aes-sym-encrypt-decrypt-gcm.md) + +[使用RSA密钥对(PKCS1模式)签名验签](https://docs.openharmony.cn/pages/v5.1/zh-cn/application-dev/security/CryptoArchitectureKit/crypto-rsa-sign-sig-verify-pkcs1.md) + + diff --git a/code/BasicFeature/Security/KeyManager/build-profile.json5 b/code/BasicFeature/Security/KeyManager/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..49f50afc4ef59767dc9e7f60b48e0992c911be2a --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/build-profile.json5 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": 12, + "compileSdkVersion": 12, + "runtimeOS": "OpenHarmony", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/code-linter.json5 b/code/BasicFeature/Security/KeyManager/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ed05653cca31b61d64cf6471529eaf50d4f70709 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/code-linter.json5 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/common_secret_key_manager.gif b/code/BasicFeature/Security/KeyManager/common_secret_key_manager.gif new file mode 100644 index 0000000000000000000000000000000000000000..5a6e91e0ad3ee83a3f67b1ca907745ab2c62cc7d Binary files /dev/null and b/code/BasicFeature/Security/KeyManager/common_secret_key_manager.gif differ diff --git a/code/BasicFeature/Security/KeyManager/entry/.gitignore b/code/BasicFeature/Security/KeyManager/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/build-profile.json5 b/code/BasicFeature/Security/KeyManager/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e7569e3056e27af38e9991b7ea73ec10f3ba8a05 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/build-profile.json5 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/hvigorfile.ts b/code/BasicFeature/Security/KeyManager/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4f43d54667f8327c367c8096bd08bb8c75aff54 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/hvigorfile.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/code/BasicFeature/Security/KeyManager/entry/obfuscation-rules.txt b/code/BasicFeature/Security/KeyManager/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/oh-package.json5 b/code/BasicFeature/Security/KeyManager/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c9cb6c8174858277c9b0d465a51547dcab16d5ff --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/oh-package.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {} +} + diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/ets/client/LocalClient.ets b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/client/LocalClient.ets new file mode 100644 index 0000000000000000000000000000000000000000..5ee352210e42d8bc5be814dd25e4f3c4fe6fa978 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/client/LocalClient.ets @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LocalMockServer } from '../server/LocalMockServer'; +import { aesGcmDecrypt, aesGcmEncrypt, generateAesKey } from '../utils/AesUtils'; +import { rsaEncryption, verify } from '../utils/RsaUtils'; +import { MessagePosition, sendProcessMessage } from '../utils/Utils'; + +const TAG: string = 'LocalClient'; + +class SignedData { + encryptedMessage: string = ""; + signedMessage: string = ""; +} + +/** + * 本地客户端进行加密、验签 + */ +export class LocalClient { + private server: LocalMockServer = new LocalMockServer(); + private publicKey: string = ""; + private aesKey: string = ""; + + // 客户端下载公钥 + async downloadPublicKey() { + let publicKeyResult: SignedData = JSON.parse(await this.server.downloadPublicKey()); + // 验证公钥签名 + if (await verify(publicKeyResult.encryptedMessage, publicKeyResult.signedMessage, + publicKeyResult.encryptedMessage)) { + this.publicKey = publicKeyResult.encryptedMessage; + } + if (this.publicKey === "") { + sendProcessMessage("获取公钥失败", MessagePosition.Right); + throw new Error('downloadPublicKey failed'); + } + sendProcessMessage("获取公钥成功", MessagePosition.Right); + } + + // 发送数据到服务端 + async sendMessageToServer(message: string): Promise { + sendProcessMessage("开始发送数据到服务端...", MessagePosition.Right); + // 消息加密 + let encryptionMessage: string = await this.encryption(message); + sendProcessMessage("发送加密数据到服务端...", MessagePosition.Right); + // 发送服务端 + await this.server.receiveMessageFromClient(encryptionMessage); + } + + // 从服务端接收数据 + async receiveMessageFromServer(): Promise { + sendProcessMessage("开始接收服务端数据...", MessagePosition.Right); + let signMessage: string = await this.server.sendMessageToClient(); + // 验证签名 + let signData: SignedData = JSON.parse(signMessage); + sendProcessMessage("开始验证签名...", MessagePosition.Right); + let isVerified: boolean = await verify(signData.encryptedMessage, signData.signedMessage, this.publicKey); + if (isVerified) { + sendProcessMessage("签名验证成功,开始解密...", MessagePosition.Right); + let decryptedMessage = await aesGcmDecrypt(signData.encryptedMessage, this.aesKey); + sendProcessMessage("解密成功,解密结果:" + decryptedMessage, MessagePosition.Right); + return decryptedMessage; + } + sendProcessMessage("签名验证失败", MessagePosition.Right); + return "签名验证失败"; + } + + // 加密 + async encryption(message: string): Promise { + if (!this.publicKey) { + sendProcessMessage("本地未找到公钥,开始下载...", MessagePosition.Right); + // 获取公钥 + await this.downloadPublicKey(); + sendProcessMessage("公钥下载成功", MessagePosition.Right); + } + if (this.aesKey === "") { + sendProcessMessage("本地未找到AES密钥,开始生成...", MessagePosition.Right); + // 生成AES密钥 + this.aesKey = await generateAesKey(); + sendProcessMessage(`AES密钥生成成功,密钥:${this.aesKey},开始发送到服务端...`, MessagePosition.Right); + // 将AES密钥发送到服务端 + await this.sendAesKeyToServer(); + } + sendProcessMessage(`客户端开始加密数据...`, MessagePosition.Right); + let encryptionResult: string = await aesGcmEncrypt(message, this.aesKey); + sendProcessMessage(`客户端加密成功,加密结果:${encryptionResult}`, MessagePosition.Right); + // 使用AES加密数据 + return encryptionResult; + } + + // 发送AES密钥到服务端 + async sendAesKeyToServer() { + sendProcessMessage(`AES密钥进行加密...`, MessagePosition.Right); + // 对AES密钥使用公钥进行加密 + let encryptedAesKey: string = await rsaEncryption(this.aesKey, this.publicKey); + sendProcessMessage(`加密成功,加密结果:${encryptedAesKey},开始发送到服务端...`, MessagePosition.Right); + // 将加密后的AES密钥发送到服务端 + await this.server.receiveKeyFromClient(encryptedAesKey); + } +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/ets/entryability/EntryAbility.ets b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..3b2f952c0de46b4376ecaf31f40514f3a5ef9870 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/KeyManager', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..0a97e21bd7a15599af76a806695860ff1eb0ebfe --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/ets/pages/KeyManager.ets b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/pages/KeyManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..63819e4f98837cb638459f737c3de0a21b4a6f83 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/pages/KeyManager.ets @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LocalClient } from '../client/LocalClient'; +import { KeyboardAvoidMode } from '@kit.ArkUI'; +import { emitter } from '@kit.BasicServicesKit'; +import { Message, messageEvent, MessagePosition, sendProcessMessage } from '../utils/Utils'; +import { inputMethod } from '@kit.IMEKit'; + +/** + * 模拟数据加密传输流程 + * 1. 请求服务端,获取公钥。 + * 2. 服务端生成RSA密钥,用私钥对公钥签名后返回给客户端。 + * 3. 客户端验证公钥签名。 + * 4. 生成AES密钥,通过公钥加密后发送到服务端 + * 5. 服务端解密并存储AES密钥 + * 6. 客户端使用AES密钥对数据进行加密,发送给服务端 + * 7. 服务端使用AES密钥对数据进行解密,存储 + * 8. 服务端使用AES密钥对数据进行加密 + * 9. 服务端使用私钥对数据签名,并发送给客户端 + * 10. 客户端使用公钥对数据验签,判断是否成功 + * 11. 验签成功后使用AES密钥解密数据 + */ + +@Entry +@Component +struct KeyManager { + @State message: string = ""; + private client: LocalClient = new LocalClient(); + @State messageArray: Message[] = []; + + aboutToAppear(): void { + this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE); + + emitter.on(messageEvent, (data: emitter.EventData) => { + let message: Message = new Message(); + message.message = data.data?.message; + message.position = data.data?.position; + this.messageArray.push(message); + }); + } + + build() { + RelativeContainer() { + Row() { + TextInput({ text: this.message, placeholder: $r('app.string.please_input') }) + .id('input') + .onChange((value: string) => { + this.message = value; + }) + .layoutWeight(4) + Button($r('app.string.send')) + .id('button') + .onClick(() => { + inputMethod.getController().stopInputSession(); + this.sendMessage(this.message); + this.message = ""; + }) + .layoutWeight(1) + .margin({ + left: 10 + }) + } + .padding({ + left: 10, + right: 10 + }) + .alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .height(60) + .alignItems(VerticalAlign.Center) + .width('100%') + .id("row_input") + + List() { + ForEach(this.messageArray, (message: Message) => { + ListItem() { + Row() { + Text(message.message) + .padding(10) + .fontColor(message.position === MessagePosition.Left ? Color.Blue : Color.Black) + .constraintSize({ + maxWidth: '80%' + }) + .borderRadius(10) + .backgroundColor("#F1F1F1") + .margin({ + left: message.position === MessagePosition.Left ? 10 : 0, + right: message.position === MessagePosition.Right ? 10 : 0 + }) + } + .width('100%') + .justifyContent(message.position === MessagePosition.Left ? FlexAlign.Start : FlexAlign.End) + .margin({ + top: 10 + }) + } + }) + }.id('list') + .scrollBar(BarState.Off) + .alignRules({ + bottom: { anchor: 'row_input', align: VerticalAlign.Top }, + top: { anchor: '__container__', align: VerticalAlign.Top }, + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End } + }) + .expandSafeArea([SafeAreaType.KEYBOARD]) + + } + .height('100%') + .width('100%') + } + + async sendMessage(message: string) { + sendProcessMessage("需要发送给服务端的数据是:" + message, MessagePosition.Right); + await this.client.sendMessageToServer(message); + + setTimeout(async () => { + await this.client.receiveMessageFromServer(); + }, 2000) + } +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/ets/server/LocalMockServer.ets b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/server/LocalMockServer.ets new file mode 100644 index 0000000000000000000000000000000000000000..da91749820b6d0e6d26362ed6779ca569215910e --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/server/LocalMockServer.ets @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Logger from '../utils/Logger'; +import { aesGcmDecrypt, aesGcmEncrypt } from '../utils/AesUtils'; +import { generateRsaKey, rsaDecryption, RsaKey, sign } from '../utils/RsaUtils'; +import { emitter } from '@kit.BasicServicesKit'; +import { messageEvent, MessagePosition, sendProcessMessage } from '../utils/Utils'; + +const TAG: string = 'LocalMockServer'; + +class SignedData { + encryptedMessage: string = ""; + signedMessage: string = ""; +} + +/** + * 模拟服务端进行RSA密钥生成、解密、签名 + */ +export class LocalMockServer { + private publicKey: string = ""; + private privateKey: string = ""; + private messageStorage: string[] = []; + private aesKey: string = ""; + + //生成证书 + async createKey() { + sendProcessMessage("模拟服务端开始生成证书", MessagePosition.Left); + let rsaKey: RsaKey | undefined = await generateRsaKey() + if (rsaKey) { + this.publicKey = rsaKey.publicKey; + this.privateKey = rsaKey.privateKey; + sendProcessMessage("模拟服务端生成证书成功,公钥:" + this.publicKey, MessagePosition.Left); + } + } + + // 模拟公钥下载 + async downloadPublicKey(): Promise { + if (this.publicKey === "") { + // 创建密钥 + await this.createKey(); + } + // 对公钥进行签名 + let signResult: string = await sign(this.publicKey, this.privateKey); + let publicKeyResult: SignedData = { encryptedMessage: this.publicKey, signedMessage: signResult }; + return JSON.stringify(publicKeyResult); + } + + async receiveKeyFromClient(encryptedAesKey: string) { + sendProcessMessage(`模拟服务端接收到AES密钥:${encryptedAesKey}`, MessagePosition.Left); + this.aesKey = await rsaDecryption(encryptedAesKey, this.privateKey); + sendProcessMessage(`模拟服务端AES密钥解密成功,AES密钥为:${this.aesKey}`, MessagePosition.Left); + Logger.info(TAG, 'receive key from client success, server aesKey:' + this.aesKey); + } + + // 获取客户端消息 + async receiveMessageFromClient(message: string): Promise { + sendProcessMessage("模拟服务端接收到数据,开始解密...", MessagePosition.Left); + // 解密 + let decryptedMessage = await aesGcmDecrypt(message, this.aesKey); + sendProcessMessage(`模拟服务端解密成功,解密结果:${decryptedMessage}`, MessagePosition.Left); + // 存储 + this.messageStorage.push(decryptedMessage); + } + + async sendMessageToClient(): Promise { + let needSendMessage: string = "这是模拟服务端返回的测试数据," + this.messageStorage[this.messageStorage.length-1]; + sendProcessMessage(`模拟服务端开始发送数据,将要发送的数据是:${needSendMessage}`, MessagePosition.Left); + sendProcessMessage(`模拟服务端开始加密数据:${needSendMessage}`, MessagePosition.Left); + let encryptedMessage: string = await aesGcmEncrypt(needSendMessage, this.aesKey); + sendProcessMessage(`模拟服务端加密成功,加密结果:${encryptedMessage}`, MessagePosition.Left); + // 签名 + sendProcessMessage(`模拟服务端开始签名数据...`, MessagePosition.Left); + let signedMessage: string = await sign(encryptedMessage, this.privateKey); + sendProcessMessage(`模拟服务端签名成功,签名结果:${signedMessage}`, MessagePosition.Left); + let signData: SignedData = { encryptedMessage: encryptedMessage, signedMessage: signedMessage }; + // 发送消息 + return JSON.stringify(signData); + } +} diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/AesUtils.ets b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/AesUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..45c469f0c11a130440b89b54ee7ebe99f51bbfc6 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/AesUtils.ets @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cryptoFramework } from "@kit.CryptoArchitectureKit"; +import Logger from "./Logger"; +import { arrayBufferToString, fromHexString, stringToUint8Array, TAG, uint8ArrayToShowStr } from "./Utils"; + +// 加密数据结构 +class EncryptionData { + aesGcmTag: string = ""; + encryptedText: string = ""; +} + +/** + * 生成AES密钥 + * @returns AES密钥的16进制字符串 + */ +export async function generateAesKey(): Promise { + try { + // 创建对称密钥生成器 + let symKeyGenerator: cryptoFramework.SymKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256'); + // 通过密钥生成器随机生成对称密钥 + let symKey = await symKeyGenerator.generateSymKey(); + // 获取对称密钥的二进制数据,输出长度为256bit的字节流 + let encodedKey: cryptoFramework.DataBlob = symKey.getEncoded(); + let data: Uint8Array = encodedKey.data; + // 将二进制数据转为16进制string。 + return uint8ArrayToShowStr(data); + } catch (error) { + Logger.error(TAG, 'AES create failed'); + return ""; + } +} + +/** + * AES解密 + * @param encryptedMessage 已经加密的数据 + * @param aesKey AES密钥 + * @returns 解密后的数据 + */ +export async function aesGcmDecrypt(encryptedMessage: string, aesKey: string): Promise { + let encryptionData: EncryptionData = JSON.parse(encryptedMessage); + try { + // 创建对称密钥生成器实例 + let symKeyGenerator: cryptoFramework.SymKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256'); + // 将AES密钥字符串转换为密钥类型 + let symKeyBlob: cryptoFramework.DataBlob = { data: fromHexString(aesKey) }; + let key: cryptoFramework.SymKey = await symKeyGenerator.convertKey(symKeyBlob); + // 指定算法名称(含密钥长度)、加密模式以及填充方法的组合 + let cipherAlgName: string = 'AES256|GCM|PKCS7'; + let cipher: cryptoFramework.Cipher = cryptoFramework.createCipher(cipherAlgName); + // 适用于GCM模式的加密参数 + let globalGcmParams: cryptoFramework.GcmParamsSpec = genGcmParamsSpec(encryptionData.aesGcmTag); + // 表示进行加密操作 + let mode: cryptoFramework.CryptoMode = cryptoFramework.CryptoMode.DECRYPT_MODE; + // Cipher中init、update、doFinal为三段式接口,需要成组使用。其中init和doFinal必选,update可选。 + // 初始化加解密的cipher对象 + await cipher.init(mode, key, globalGcmParams); + // 将明文转换为Uint8Array,用于后续加密操作 + let plainText: cryptoFramework.DataBlob = { data: fromHexString(encryptionData.encryptedText) }; + // 分段更新加密或者解密数据操作,可选方法 + let cipherTextBlob: cryptoFramework.DataBlob = await cipher.update(plainText); + await cipher.doFinal(null); + let tmpArr: Uint8Array = cipherTextBlob.data; + // 将加密后的密文转换为字符串 + let decryptedText: string = arrayBufferToString(tmpArr); + return decryptedText; + } catch (error) { + Logger.error(TAG, `AES decrypt failed, ${error.code}, ${error.message}`); + return ""; + } +} + +/** + * AES加密 + * @param textString 需要加密的数据 + * @param aesKey AES密钥 + * @returns + */ +export async function aesGcmEncrypt(textString: string, aesKey: string): Promise { + try { + // 创建对称密钥生成器实例 + let symKeyGenerator: cryptoFramework.SymKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256'); + // 将AES密钥字符串转换为密钥类型 + let symKeyBlob: cryptoFramework.DataBlob = { data: fromHexString(aesKey) }; + let key: cryptoFramework.SymKey = await symKeyGenerator.convertKey(symKeyBlob); + // 指定算法名称(含密钥长度)、加密模式以及填充方法的组合 + let cipherAlgName: string = 'AES256|GCM|PKCS7'; + let cipher: cryptoFramework.Cipher = cryptoFramework.createCipher(cipherAlgName); + // 适用于GCM模式的加密参数 + let globalGcmParams: cryptoFramework.GcmParamsSpec = genGcmParamsSpec(); + // 表示进行加密操作 + let mode: cryptoFramework.CryptoMode = cryptoFramework.CryptoMode.ENCRYPT_MODE; + // Cipher中init、update、doFinal为三段式接口,需要成组使用。其中init和doFinal必选,update可选。 + // 初始化加解密的cipher对象 + await cipher.init(mode, key, globalGcmParams); + // 将明文转换为Uint8Array,用于后续加密操作 + let plainText: cryptoFramework.DataBlob = { data: stringToUint8Array(textString) }; + // 分段更新加密或者解密数据操作,可选方法 + let cipherTextBlob: cryptoFramework.DataBlob = await cipher.update(plainText); + let tmpArr: Uint8Array = cipherTextBlob.data; + // 将加密后的密文转换为16进制字符串 + let encryptedText: string = uint8ArrayToShowStr(tmpArr); + // 用于处理剩余数据和本次传入的数据,并最终结束加密或解密操作。 + // 对于GCM模式的对称加密:一次加密流程中,如果将每一次update和doFinal的结果拼接起来,会得到“密文+authTag”,即末尾的16字节(GCM模式)或12字节(CCM模式)是authTag, + // 而其余部分均为密文。(也就是说,如果doFinal的data参数传入null,则doFinal的结果就是authTag) + // authTag需要填入解密时的GcmParamsSpec;密文则作为解密时的入参data。 + let authTag: cryptoFramework.DataBlob = await cipher.doFinal(null); + let tmoTagArr: Uint8Array = authTag.data; + let aesGcmTag: string = uint8ArrayToShowStr(tmoTagArr); + // 将AES的加密结果转换为JSON对象 + let aesEncryptResult: EncryptionData = { aesGcmTag: aesGcmTag, encryptedText: encryptedText }; + return JSON.stringify(aesEncryptResult); + } catch (error) { + Logger.error(TAG, `AES encrypt failed, ${error.code}, ${error.message}`); + return ""; + } +} + +/** + * AES加密参数 + * @param authTag 加解密参数,加密数据时不需要传参,会在调用cipher的doFinal方法时生成,解密时需要传入 + * @returns + */ +export function genGcmParamsSpec(authTag?: string): cryptoFramework.GcmParamsSpec { + let arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 12 bytes + let dataIv = new Uint8Array(arr); + let ivBlob: cryptoFramework.DataBlob = { data: dataIv }; + + arr = [0, 0, 0, 0, 0, 0, 0, 0]; // 8 bytes + let dataAad = new Uint8Array(arr); + let aadBlob: cryptoFramework.DataBlob = { data: dataAad }; + + arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 16 bytes + let dataTag = new Uint8Array(arr); + // GCM的authTag在加密时从doFinal结果中获取,在解密时填入init函数的params参数中 + let tagBlob: cryptoFramework.DataBlob = { data: authTag ? fromHexString(authTag) : dataTag }; + + let gcmParamsSpec: cryptoFramework.GcmParamsSpec = { + iv: ivBlob, + aad: aadBlob, + authTag: tagBlob, + algName: 'GcmParamsSpec' + }; + return gcmParamsSpec; +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/Logger.ets b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/Logger.ets new file mode 100644 index 0000000000000000000000000000000000000000..2ce64f4033b709ae7da1feac2ccae81c32ae04af --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/Logger.ets @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const TAG = 'KeyManager'; + +class Logger { + private domain: number; + private prefix: string; + private format: string = '%{public}s, %{public}s'; + + constructor(prefix: string) { + this.prefix = prefix; + this.domain = 0xFF00; + } + + debug(...args: string[]): void { + hilog.debug(this.domain, this.prefix, this.format, args); + } + + info(...args: string[]): void { + hilog.info(this.domain, this.prefix, this.format, args); + } + + warn(...args: string[]): void { + hilog.warn(this.domain, this.prefix, this.format, args); + } + + error(...args: string[]): void { + hilog.error(this.domain, this.prefix, this.format, args); + } +} + +export default new Logger(TAG); \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/RsaUtils.ets b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/RsaUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..2e401048d959e5228cad45bb7b9e9cefd8e7ab84 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/RsaUtils.ets @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { cryptoFramework } from "@kit.CryptoArchitectureKit"; +import Logger from "./Logger"; +import { fromHexString, stringToUint8Array, TAG, uint8ArrayToShowStr } from "./Utils"; + +// RSA密钥对 +export interface RsaKey { + publicKey: string; + privateKey: string; +} + +/** + * 生成RSA密钥对 + * @returns RSA密钥对 + */ +export async function generateRsaKey(): Promise { + try { + // 创建非对称密钥生成器 + let rsaKeyGenerator: cryptoFramework.AsyKeyGenerator = cryptoFramework.createAsyKeyGenerator('RSA1024'); + // 通过密钥生成器随机生成非对称密钥 + let keyPair: cryptoFramework.KeyPair = await rsaKeyGenerator.generateKeyPair(); + // 获取非对称密钥的二进制数据 + let encodedPriKey: cryptoFramework.DataBlob = keyPair.priKey.getEncoded(); + // 私钥 + let priKeyData: Uint8Array = encodedPriKey.data; + let encodedPubKey: cryptoFramework.DataBlob = keyPair.pubKey.getEncoded(); + // 公钥 + let pubKeyData: Uint8Array = encodedPubKey.data; + let rsaKey: RsaKey = { + privateKey: uint8ArrayToShowStr(priKeyData), + publicKey: uint8ArrayToShowStr(pubKeyData) + }; + return rsaKey; + } catch (error) { + Logger.error(TAG, 'RSA create failed'); + return undefined; + } +} + +/** + * RSA加密 + * @param data 需要加密的数据 + * @param publicKey 公钥 + * @returns 完成加密的数据 + */ +export async function rsaEncryption(data: string, publicKey: string): Promise { + try { + // 创建非对称密钥生成器实例 + let rsaKeyGenerator: cryptoFramework.AsyKeyGenerator = cryptoFramework.createAsyKeyGenerator('RSA1024'); + // 将RSA密钥字符串转换为密钥类型 + let pubKeyBlob: cryptoFramework.DataBlob = { data: fromHexString(publicKey) }; + let key: cryptoFramework.PubKey = (await rsaKeyGenerator.convertKey(pubKeyBlob, null)).pubKey; + // 指定算法名称(含密钥长度)、加密模式以及填充方法的组合 + let cipherAlgName: string = 'RSA1024|PKCS1'; + let cipher: cryptoFramework.Cipher = cryptoFramework.createCipher(cipherAlgName); + // 表示进行加密操作 + let mode: cryptoFramework.CryptoMode = cryptoFramework.CryptoMode.ENCRYPT_MODE; + // Cipher中init、update、doFinal为三段式接口,需要成组使用。其中init和doFinal必选,update可选。 + // 初始化加解密的cipher对象 + await cipher.init(mode, key, null); + // 将明文转换为Uint8Array,用于后续加密操作 + let plainText: cryptoFramework.DataBlob = { data: fromHexString(data) }; + // 用于处理剩余数据和本次传入的数据,并最终结束加密或解密操作。 + // 在RSA非对称加解密中,doFinal加解密本次传入的数据,通过注册回调函数获取加密或者解密数据。如果数据量较大,可以多次调用doFinal,拼接结果得到完整的明文/密文。 + let encryptBlob: cryptoFramework.DataBlob = await cipher.doFinal(plainText); + return uint8ArrayToShowStr(encryptBlob.data); + } catch (err) { + Logger.error(TAG, `RSA encryption failed, ${err.code}, ${err.message}`); + } + return ""; +} + + +/** + * RSA解密 + * @param encryptedData 经过加密的数据 + * @param privateKey 私钥 + * @returns 解密后的数据 + */ +export async function rsaDecryption(encryptedData: string, privateKey: string): Promise { + try { + // 创建非对称密钥生成器实例 + let rsaKeyGenerator: cryptoFramework.AsyKeyGenerator = cryptoFramework.createAsyKeyGenerator('RSA1024'); + // 将RSA密钥字符串转换为密钥类型 + let priKeyBlob: cryptoFramework.DataBlob = { data: fromHexString(privateKey) }; + let key: cryptoFramework.PriKey = (await rsaKeyGenerator.convertKey(null, priKeyBlob)).priKey; + let cipherAlgName: string = 'RSA1024|PKCS1'; + let cipher: cryptoFramework.Cipher = cryptoFramework.createCipher(cipherAlgName); + // 表示进行解密操作 + let mode: cryptoFramework.CryptoMode = cryptoFramework.CryptoMode.DECRYPT_MODE; + // Cipher中init、update、doFinal为三段式接口,需要成组使用。其中init和doFinal必选,update可选。 + // 初始化加解密的cipher对象 + await cipher.init(mode, key, null); + // 将秘文转换为Uint8Array,用于后续解密操作 + let plainText: cryptoFramework.DataBlob = { data: fromHexString(encryptedData) }; + let decryptBlob: cryptoFramework.DataBlob = await cipher.doFinal(plainText); + return uint8ArrayToShowStr(decryptBlob.data); + } catch (err) { + Logger.error(TAG, `RSA decryption failed, ${err.code}, ${err.message}`); + } + return ""; +} + +/** + * 验证签名 + * @param encryptedData 待验证的数据 + * @param singedData 签名信息 + * @param publicKey 公钥 + * @returns 签名验证是否通过 + */ +export async function verify(encryptedData: string, singedData: string, publicKey: string): Promise { + try { + let verifyer = cryptoFramework.createVerify('RSA1024|PKCS1|SHA256'); + // 创建非对称密钥生成器实例 + let rsaKeyGenerator: cryptoFramework.AsyKeyGenerator = cryptoFramework.createAsyKeyGenerator('RSA1024'); + // 将RSA密钥字符串转换为密钥类型 + let pubKeyBlob: cryptoFramework.DataBlob = { data: fromHexString(publicKey) }; + let key: cryptoFramework.PubKey = (await rsaKeyGenerator.convertKey(pubKeyBlob, null)).pubKey; + let encryptedBlob: Uint8Array = stringToUint8Array(encryptedData); + let signedBlob: Uint8Array = fromHexString(singedData); + await verifyer.init(key); + let result: boolean = await verifyer.verify({ data: encryptedBlob }, { data: signedBlob }); + return result; + } catch (err) { + Logger.error(TAG, `RSA verify failed, ${err.code}, ${err.message}`); + } + return false; +} + +/** + * 签名 + * @param data 需要签名的数据 + * @param privateKey 私钥 + * @returns 签名信息 + */ +export async function sign(data: string, privateKey: string): Promise { + try { + let signer = cryptoFramework.createSign('RSA1024|PKCS1|SHA256'); + // 创建非对称密钥生成器实例 + let rsaKeyGenerator: cryptoFramework.AsyKeyGenerator = cryptoFramework.createAsyKeyGenerator('RSA1024'); + // 将RSA密钥字符串转换为密钥类型 + let priKeyBlob: cryptoFramework.DataBlob = { data: fromHexString(privateKey) }; + let key: cryptoFramework.PriKey = (await rsaKeyGenerator.convertKey(null, priKeyBlob)).priKey; + await signer.init(key); + let signBlob = stringToUint8Array(data); + let signedBlob = await signer.sign({ data: signBlob }); + let tmpArr: Uint8Array = signedBlob.data; + let rsaSignedBlobString = uint8ArrayToShowStr(tmpArr); + return rsaSignedBlobString; + } catch (error) { + Logger.error(TAG, `RSA sign failed, ${error.code}, ${error.message}`); + return ""; + } +} diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/Utils.ets b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/Utils.ets new file mode 100644 index 0000000000000000000000000000000000000000..134c8b392ea7d23e4baa5571ae63661966e095cd --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/ets/utils/Utils.ets @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { buffer, util } from '@kit.ArkTS'; +import { emitter } from '@kit.BasicServicesKit'; + +export const TAG: string = "KeyManager"; + +export enum MessagePosition { + Left, Right +} + +export class Message { + message: string = ""; + position: MessagePosition = MessagePosition.Right; +} + +export const messageEvent: emitter.InnerEvent = { + eventId: 1 +} + +export function sendProcessMessage(message: string, position: MessagePosition) { + emitter.emit(messageEvent, { + data: { + message: message, + position: position + } + }) +} + +// 字节流以16进制字符串输出 +export function uint8ArrayToShowStr(uint8Array: Uint8Array): string { + let hexString: string = ''; + for (let i = 0; i < uint8Array.length; i++) { + let char = ('00' + uint8Array[i].toString(16)).slice(-2); + hexString += char; + } + return hexString; +} + +// 16进制字符串转字节流 +export function fromHexString(hexString: string): Uint8Array { + return new Uint8Array(buffer.from(hexString, 'hex').buffer); +} + + +// 字节流转字符串 +export function arrayBufferToString(uint8Array: Uint8Array): string { + let textDecoderOptions: util.TextDecoderOptions = { + fatal: false, + ignoreBOM: true + } + let decodeToStringOptions: util.DecodeToStringOptions = { + stream: false + } + let textDecoder = util.TextDecoder.create('utf-8', textDecoderOptions); + let retStr = textDecoder.decodeToString(new Uint8Array(uint8Array), decodeToStringOptions); + console.info('字节流转成可理解的字符串:' + retStr); + return retStr; +} + +// 可理解的字符串转成字节流 +export function stringToUint8Array(str: string): Uint8Array { + console.info('字符串转成字节流:' + new Uint8Array(buffer.from(str, 'utf-8').buffer)); + return new Uint8Array(buffer.from(str, 'utf-8').buffer); +} + diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/module.json5 b/code/BasicFeature/Security/KeyManager/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4144486d1af4c03b0d767cce1cda86fc0d697f91 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/module.json5 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/element/color.json b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/element/float.json b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..33ea22304f9b1485b5f22d811023701b5d4e35b6 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/element/string.json b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..cc45e90d0cd1673ca6fe01eefed69651bb57bd43 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/element/string.json @@ -0,0 +1,24 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "通用密钥管理" + }, + { + "name": "EntryAbility_desc", + "value": "通用密钥管理" + }, + { + "name": "EntryAbility_label", + "value": "通用密钥管理" + }, + { + "name": "please_input", + "value": "通用密钥管理" + }, + { + "name": "send", + "value": "发送" + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/background.png b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/background.png differ diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/foreground.png b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/foreground.png differ diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/layered_image.json b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/startIcon.png b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/media/startIcon.png differ diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/profile/backup_config.json b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/profile/main_pages.json b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..189fe9b252ee8128beb79ca720b44d95c088f249 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/KeyManager" + ] +} diff --git a/code/BasicFeature/Security/KeyManager/entry/src/main/resources/dark/element/color.json b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/ets/test/Ability.test.ets b/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..1e32ab19f4ebe2a8aee4bffd470098787cd99c1e --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import { beforeAll, describe, it } from '@ohos/hypium'; +import Logger from '../utils/Logger'; +import { Driver, ON } from '@kit.TestKit'; + +const TAG = '[Sample_KeyManager]'; +const DELAY_MS_500: number = 500; // 指定500ms的延迟 +const DELAY_MS_2000: number = 2000; // 指定2000ms的延迟 + +export default function abilityTest() { + const driver: Driver = Driver.create(); + describe('ActsAbilityTest', () => { + beforeAll(async (done: Function) => { + Logger.info(TAG, 'beforeAll begin'); + let abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + try { + await abilityDelegator.startAbility({ + bundleName: 'com.samples.keymanager', + abilityName: 'EntryAbility' + }); + } catch (err) { + Logger.error(TAG, `beforeAll exception = ${JSON.stringify(err)}`); + } + await driver.delayMs(DELAY_MS_2000); + Logger.info(TAG, 'beforeAll end'); + done(); + }); + + // 查看页面显示是否正常,是否可以正常进行页面切换 + it('通用密钥管理', 0, async (done: Function) => { + await driver.assertComponentExist(ON.id('input')); + await driver.assertComponentExist(ON.id('button')); + await driver.assertComponentExist(ON.id('list')); + + let textInput = await driver.findComponent(ON.id('input')); + await driver.delayMs(DELAY_MS_500); + await textInput.inputText("测试数据"); + await driver.delayMs(DELAY_MS_500); + let button = await driver.findComponent(ON.id('button')); + await driver.delayMs(DELAY_MS_500); + await button.click(); + await driver.delayMs(DELAY_MS_500); + let list = await driver.findComponent(ON.id('list')); + await driver.delayMs(DELAY_MS_500); + await list.scrollToBottom(); + await driver.delayMs(DELAY_MS_500); + await textInput.inputText("测试数据"); + await driver.delayMs(DELAY_MS_500); + await button.click(); + await driver.delayMs(DELAY_MS_500); + await list.scrollToBottom(); + done(); + }) + }) +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/ets/test/List.test.ets b/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..1eac52fcebe8958e19a7b8fed2e8f39c520a3e42 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/ets/utils/Logger.ets b/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/ets/utils/Logger.ets new file mode 100644 index 0000000000000000000000000000000000000000..eaac4acd2b2fc38673f82120eddbe99e7a22c38c --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/ets/utils/Logger.ets @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import hilog from '@ohos.hilog'; + +class Logger { + private domain: number = 0xF811; + private prefix: string = ''; + private format: string = '%{public}s, %{public}s'; + + constructor(prefix: string) { + this.prefix = prefix; + this.domain = 0xF811; + } + + debug(...args: string[]): void { + hilog.debug(this.domain, this.prefix, this.format, args); + } + + info(...args: string[]): void { + hilog.info(this.domain, this.prefix, this.format, args); + } + + warn(...args: string[]): void { + hilog.warn(this.domain, this.prefix, this.format, args); + } + + error(...args: string[]): void { + hilog.error(this.domain, this.prefix, this.format, args); + } +} + +export default new Logger('[Sample_KeyManager]'); \ No newline at end of file diff --git a/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/module.json5 b/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c3fd9dda3040d888d9d8b0b62bcb5d3b6fbeb614 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/entry/src/ohosTest/module.json5 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/code/BasicFeature/Security/KeyManager/hvigor/hvigor-config.json5 b/code/BasicFeature/Security/KeyManager/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..a63d34ae5ce5833b3874807e2b8d472687c6c5bf --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.0.4", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/code/BasicFeature/Security/KeyManager/hvigorfile.ts b/code/BasicFeature/Security/KeyManager/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..2a5e543f190732c159beb574dfc9fa37bc94e156 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/hvigorfile.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/code/BasicFeature/Security/KeyManager/oh-package.json5 b/code/BasicFeature/Security/KeyManager/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..feee3e2fc0ddc7df19a7498269e29d53fdd944e7 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/oh-package.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "modelVersion": "5.0.4", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.21", + "@ohos/hamock": "1.0.0" + } +} diff --git a/code/BasicFeature/Security/KeyManager/ohosTest.md b/code/BasicFeature/Security/KeyManager/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..9cfdbc6529354b86bce4922ed55a7ab7b8887e81 --- /dev/null +++ b/code/BasicFeature/Security/KeyManager/ohosTest.md @@ -0,0 +1,12 @@ +# 通用密钥管理 测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | 测试结果 | +|-----------------|-----------------|---------------------|--------------------| -------- |---------| +| 首次加密先生成密钥再进行加解密 | 成功拉起应用,进入案例页面 | 点击输入框,随意输入内容,点击发送按钮 | 页面显示从生成密钥到加解密的完整流程 | 是 | 是 | +| 第二次加密只有加解密流程 | 在案例页面,并且已经加密过一次 | 点击输入框,随意输入内容,点击发送按钮 | 页面显示加解密的完整流程 | 是 | 是 | + + + +