Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenHarmony
Docs
提交
491a711e
D
Docs
项目概览
OpenHarmony
/
Docs
大约 1 年 前同步成功
通知
159
Star
292
Fork
28
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
D
Docs
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
491a711e
编写于
7月 04, 2023
作者:
W
wutiantian_gitee
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
modify doc of cryptFramework
Signed-off-by:
N
wutiantian_gitee
<
1159609162@qq.com
>
上级
101462d4
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
511 addition
and
30 deletion
+511
-30
en/application-dev/security/cryptoFramework-guidelines.md
en/application-dev/security/cryptoFramework-guidelines.md
+7
-8
zh-cn/application-dev/reference/apis/js-apis-cryptoFramework.md
...application-dev/reference/apis/js-apis-cryptoFramework.md
+11
-11
zh-cn/application-dev/security/cryptoFramework-guidelines.md
zh-cn/application-dev/security/cryptoFramework-guidelines.md
+367
-8
zh-cn/application-dev/security/cryptoFramework-overview.md
zh-cn/application-dev/security/cryptoFramework-overview.md
+126
-3
未找到文件。
en/application-dev/security/cryptoFramework-guidelines.md
浏览文件 @
491a711e
...
...
@@ -58,12 +58,11 @@ function generateAsyKey() {
// Use the key generator to randomly generate an asymmetric key pair.
let
keyGenPromise
=
rsaGenerator
.
generateKeyPair
();
keyGenPromise
.
then
(
keyPair
=>
{
globalKeyPair
=
keyPair
;
let
pubKey
=
globalKeyPair
.
pubKey
;
let
priKey
=
globalKeyPair
.
priKey
;
let
pubKey
=
keyPair
.
pubKey
;
let
priKey
=
keyPair
.
priKey
;
// Obtain the binary data of the asymmetric key pair.
pkBlob
=
pubKey
.
getEncoded
();
skBlob
=
priKey
.
getEncoded
();
let
pkBlob
=
pubKey
.
getEncoded
();
let
skBlob
=
priKey
.
getEncoded
();
AlertDialog
.
show
({
message
:
"
pk bin data
"
+
pkBlob
.
data
}
);
AlertDialog
.
show
({
message
:
"
sk bin data
"
+
skBlob
.
data
}
);
})
...
...
@@ -145,7 +144,7 @@ function convertEccAsyKey() {
let
priKeyArray
=
new
Uint8Array
([
48
,
49
,
2
,
1
,
1
,
4
,
32
,
115
,
56
,
137
,
35
,
207
,
0
,
60
,
191
,
90
,
61
,
136
,
105
,
210
,
16
,
27
,
4
,
171
,
57
,
10
,
61
,
123
,
40
,
189
,
28
,
34
,
207
,
236
,
22
,
45
,
223
,
10
,
189
,
160
,
10
,
6
,
8
,
42
,
134
,
72
,
206
,
61
,
3
,
1
,
7
]);
let
pubKeyBlob
=
{
data
:
pubKeyArray
};
let
priKeyBlob
=
{
data
:
priKeyArray
};
let
generator
=
cryptoFrame
W
ork
.
createAsyKeyGenerator
(
"
ECC256
"
);
let
generator
=
cryptoFrame
w
ork
.
createAsyKeyGenerator
(
"
ECC256
"
);
generator
.
convertKey
(
pubKeyBlob
,
priKeyBlob
,
(
error
,
data
)
=>
{
if
(
error
)
{
AlertDialog
.
show
({
message
:
"
Convert keypair fail
"
});
...
...
@@ -884,7 +883,7 @@ function stringToUint8Array(str) {
}
// Encrypt the message in promise mode.
function
encryptMessagePro
M
ise
()
{
function
encryptMessagePro
m
ise
()
{
// Create an AsyKeyGenerator instance.
let
rsaGenerator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
RSA1024|PRIMES_2
"
);
// Create a Cipher instance.
...
...
@@ -927,7 +926,7 @@ function encryptMessageCallback() {
}
// Encrypt and decrypt the message in promise mode.
function
decryptMessagePro
M
ise
()
{
function
decryptMessagePro
m
ise
()
{
// Create an AsyKeyGenerator instance.
let
rsaGenerator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
RSA1024|PRIMES_2
"
);
// Create a Cipher instance for encryption.
...
...
zh-cn/application-dev/reference/apis/js-apis-cryptoFramework.md
浏览文件 @
491a711e
...
...
@@ -58,7 +58,7 @@ buffer数组。
| 名称 | 类型 | 可读 | 可写 | 说明 |
| ---- | --------------------- | ---- | ---- | ------------------------------------------------------------ |
| iv |
[
DataBlob
](
#datablob
)
| 是 | 是 | 指明加解密参数iv。常见取值如下:
<br/>
- AES的CBC
\|
CTR
\|
OFB
\|
CFB模式:iv长度为16字节
<br/>
- 3DES的CBC
\|
OFB
\|
CFB模式:iv长度为8字节 |
| iv |
[
DataBlob
](
#datablob
)
| 是 | 是 | 指明加解密参数iv。常见取值如下:
<br/>
- AES的CBC
\|
CTR
\|
OFB
\|
CFB模式:iv长度为16字节
<br/>
- 3DES的CBC
\|
OFB
\|
CFB模式:iv长度为8字节
<br/>
- SM4的CBC
\|
CTR
\|
OFB
\|
CFB模式:iv长度为16字节。
|
> **说明:**
>
...
...
@@ -1297,7 +1297,7 @@ createCipher(transformation: string): Cipher
> **说明:**
>
> 1. 目前对称加解密中,PKCS5和PKCS7的实现相同,其padding长度和分组长度保持一致(即PKCS5和PKCS7在3DES中均按照8字节填充,在AES中均按照16字节填充),另有NoPadding表示不填充。<br/>开发者需要自行了解密码学不同分组模式的差异,以便选择合适的参数规格。例如选择ECB和CBC模式时,建议启用填充,否则必须确保明文长度是分组大小的整数倍;选择其他模式时,可以不启用填充,此时密文长度和明文长度一致(即可能不是分组大小的整数倍)。
> 2. 使用RSA进行非对称加解密时,必须创建两个Cipher对象分别进行加密和解密操作,而不能对同一个Cipher对象进行加解密。对称加解密没有此要求(即只要算法规格一样,可以对同一个Cipher对象进行加解密操作)。
> 2. 使用RSA
、SM2
进行非对称加解密时,必须创建两个Cipher对象分别进行加密和解密操作,而不能对同一个Cipher对象进行加解密。对称加解密没有此要求(即只要算法规格一样,可以对同一个Cipher对象进行加解密操作)。
**返回值:**
...
...
@@ -1335,7 +1335,7 @@ try {
一次完整的加/解密流程在对称加密和非对称加密中略有不同:
-
对称加解密:init为必选,update为可选(且允许多次update加/解密大数据),doFinal为必选;doFinal结束后可以重新init开始新一轮加/解密流程。
-
RSA非对称加解密:init为必选,不支持update操作,doFinal为必选(允许连续多次doFinal加/解密大数据);RSA不支持重复init,切换加解密模式或填充方式时,需要重新创建Cipher对象。
-
RSA
、SM2
非对称加解密:init为必选,不支持update操作,doFinal为必选(允许连续多次doFinal加/解密大数据);RSA不支持重复init,切换加解密模式或填充方式时,需要重新创建Cipher对象。
### 属性
...
...
@@ -1447,7 +1447,7 @@ update(data: DataBlob, callback: AsyncCallback\<DataBlob>): void
> 2. 根据数据量,可以不调用update(即[init](#init-2)完成后直接调用[doFinal](#dofinal-2))或多次调用update。<br/>
> 算法库目前没有对update(单次或累计)的数据量设置大小限制,建议对于大数据量的对称加解密,采用多次update的方式传入数据。<br/>
> AES使用多次update操作的示例代码详见开发指导“[使用加解密操作](../../security/cryptoFramework-guidelines.md#使用加解密操作)”。
> 3. RSA非对称加解密不支持update操作。
> 3. RSA
、SM2
非对称加解密不支持update操作。
**系统能力:**
SystemCapability.Security.CryptoFramework
...
...
@@ -1508,7 +1508,7 @@ update(data: DataBlob): Promise\<DataBlob>
> 2. 根据数据量,可以不调用update(即[init](#init-2)完成后直接调用[doFinal](#dofinal-2))或多次调用update。<br/>
> 算法库目前没有对update(单次或累计)的数据量设置大小限制,建议对于大数据量的对称加解密,可以采用多次update的方式传入数据。<br/>
> AES使用多次update操作的示例代码详见开发指导“[使用加解密操作](../../security/cryptoFramework-guidelines.md#使用加解密操作)”。
> 3. RSA非对称加解密不支持update操作。
> 3. RSA
、SM2
非对称加解密不支持update操作。
**系统能力:**
SystemCapability.Security.CryptoFramework
...
...
@@ -1570,14 +1570,14 @@ doFinal(data: DataBlob, callback: AsyncCallback\<DataBlob>): void
-
对于GCM和CCM模式的对称加密:一次加密流程中,如果将每一次update和doFinal的结果拼接起来,会得到“密文+authTag”,即末尾的16字节(GCM模式)或12字节(CCM模式)是authTag,而其余部分均为密文。(也就是说,如果doFinal的data参数传入null,则doFinal的结果就是authTag)
<br/>
authTag需要填入解密时的
[
GcmParamsSpec
](
#gcmparamsspec
)
或
[
CcmParamsSpec
](
#ccmparamsspec
)
;密文则作为解密时的入参data。
-
对于其他模式的对称加解密、GCM和CCM模式的对称解密:一次加/解密流程中,每一次update和doFinal的结果拼接起来,得到完整的明文/密文。
(2)在RSA非对称加解密中,doFinal加/解密本次传入的数据,通过注册回调函数获取加密或者解密数据。如果数据量较大,可以多次调用doFinal,拼接结果得到完整的明文/密文。
(2)在RSA
、SM2
非对称加解密中,doFinal加/解密本次传入的数据,通过注册回调函数获取加密或者解密数据。如果数据量较大,可以多次调用doFinal,拼接结果得到完整的明文/密文。
> **说明:**
>
> 1. 对称加解密中,调用doFinal标志着一次加解密流程已经完成,即[Cipher](#cipher)实例的状态被清除,因此当后续开启新一轮加解密流程时,需要重新调用[init()](init-2)并传入完整的参数列表进行初始化<br/>(比如即使是对同一个Cipher实例,采用同样的对称密钥,进行加密然后解密,则解密中调用init的时候仍需填写params参数,而不能直接省略为null)。
> 2. 如果遇到解密失败,需检查加解密数据和[init](#init-2)时的参数是否匹配,包括GCM模式下加密得到的authTag是否填入解密时的GcmParamsSpec等。
> 3. doFinal的结果可能为null,因此使用.data字段访问doFinal结果的具体数据前,请记得先判断结果是否为null,避免产生异常。
> 4. RSA非对称加解密时多次doFinal操作的示例代码详见开发指导“[使用加解密操作](../../security/cryptoFramework-guidelines.md#使用加解密操作)”。
> 4. RSA
、SM2
非对称加解密时多次doFinal操作的示例代码详见开发指导“[使用加解密操作](../../security/cryptoFramework-guidelines.md#使用加解密操作)”。
**系统能力:**
SystemCapability.Security.CryptoFramework
...
...
@@ -1626,14 +1626,14 @@ doFinal(data: DataBlob): Promise\<DataBlob>
-
对于GCM和CCM模式的对称加密:一次加密流程中,如果将每一次update和doFinal的结果拼接起来,会得到“密文+authTag”,即末尾的16字节(GCM模式)或12字节(CCM模式)是authTag,而其余部分均为密文。(也就是说,如果doFinal的data参数传入null,则doFinal的结果就是authTag)
<br/>
authTag需要填入解密时的
[
GcmParamsSpec
](
#gcmparamsspec
)
或
[
CcmParamsSpec
](
#ccmparamsspec
)
;密文则作为解密时的入参data。
-
对于其他模式的对称加解密、GCM和CCM模式的对称解密:一次加/解密流程中,每一次update和doFinal的结果拼接起来,得到完整的明文/密文。
(2)在RSA非对称加解密中,doFinal加/解密本次传入的数据,通过Promise获取加密或者解密数据。如果数据量较大,可以多次调用doFinal,拼接结果得到完整的明文/密文。
(2)在RSA
、SM2
非对称加解密中,doFinal加/解密本次传入的数据,通过Promise获取加密或者解密数据。如果数据量较大,可以多次调用doFinal,拼接结果得到完整的明文/密文。
> **说明:**
>
> 1. 对称加解密中,调用doFinal标志着一次加解密流程已经完成,即[Cipher](#cipher)实例的状态被清除,因此当后续开启新一轮加解密流程时,需要重新调用[init()](init-2)并传入完整的参数列表进行初始化<br/>(比如即使是对同一个Cipher实例,采用同样的对称密钥,进行加密然后解密,则解密中调用init的时候仍需填写params参数,而不能直接省略为null)。
> 2. 如果遇到解密失败,需检查加解密数据和[init](#init-2)时的参数是否匹配,包括GCM模式下加密得到的authTag是否填入解密时的GcmParamsSpec等。
> 3. doFinal的结果可能为null,因此使用.data字段访问doFinal结果的具体数据前,请记得先判断结果是否为null,避免产生异常。
> 4. RSA非对称加解密时多次doFinal操作的示例代码详见开发指导“[使用加解密操作](../../security/cryptoFramework-guidelines.md#使用加解密操作)”。
> 4. RSA
、SM2
非对称加解密时多次doFinal操作的示例代码详见开发指导“[使用加解密操作](../../security/cryptoFramework-guidelines.md#使用加解密操作)”。
**系统能力:**
SystemCapability.Security.CryptoFramework
...
...
@@ -1820,7 +1820,7 @@ Sign实例生成。<br/>支持的规格详见框架概述“[签名验签规格]
| 参数名 | 类型 | 必填 | 说明 |
| ------- | ------ | ---- | ------------------------------------------------------------ |
| algName | string | 是 | 指定签名算法:RSA,ECC或DSA。使用RSA PKCS1模式时需要设置摘要,使用RSA PSS模式时需要设置摘要和掩码摘要。 |
| algName | string | 是 | 指定签名算法:RSA,
SM2,
ECC或DSA。使用RSA PKCS1模式时需要设置摘要,使用RSA PSS模式时需要设置摘要和掩码摘要。 |
**返回值**
:
...
...
@@ -2207,7 +2207,7 @@ Verify实例生成。<br/>支持的规格详见框架概述“[签名验签规
| 参数名 | 类型 | 必填 | 说明 |
| ------- | ------ | ---- | ------------------------------------------------------------ |
| algName | string | 是 | 指定签名算法:RSA,ECC或DSA。使用RSA PKCS1模式时需要设置摘要,使用RSA PSS模式时需要设置摘要和掩码摘要。 |
| algName | string | 是 | 指定签名算法:RSA,
SM2,
ECC或DSA。使用RSA PKCS1模式时需要设置摘要,使用RSA PSS模式时需要设置摘要和掩码摘要。 |
**返回值**
:
...
...
zh-cn/application-dev/security/cryptoFramework-guidelines.md
浏览文件 @
491a711e
...
...
@@ -60,12 +60,11 @@ function generateAsyKey() {
// 通过非对称密钥生成器,随机生成非对称密钥
let
keyGenPromise
=
rsaGenerator
.
generateKeyPair
();
keyGenPromise
.
then
(
keyPair
=>
{
globalKeyPair
=
keyPair
;
let
pubKey
=
globalKeyPair
.
pubKey
;
let
priKey
=
globalKeyPair
.
priKey
;
let
pubKey
=
keyPair
.
pubKey
;
let
priKey
=
keyPair
.
priKey
;
// 获取非对称密钥的二进制数据
pkBlob
=
pubKey
.
getEncoded
();
skBlob
=
priKey
.
getEncoded
();
let
pkBlob
=
pubKey
.
getEncoded
();
let
skBlob
=
priKey
.
getEncoded
();
AlertDialog
.
show
({
message
:
"
pk bin data
"
+
pkBlob
.
data
}
);
AlertDialog
.
show
({
message
:
"
sk bin data
"
+
skBlob
.
data
}
);
})
...
...
@@ -147,7 +146,7 @@ function convertEccAsyKey() {
let
priKeyArray
=
new
Uint8Array
([
48
,
49
,
2
,
1
,
1
,
4
,
32
,
115
,
56
,
137
,
35
,
207
,
0
,
60
,
191
,
90
,
61
,
136
,
105
,
210
,
16
,
27
,
4
,
171
,
57
,
10
,
61
,
123
,
40
,
189
,
28
,
34
,
207
,
236
,
22
,
45
,
223
,
10
,
189
,
160
,
10
,
6
,
8
,
42
,
134
,
72
,
206
,
61
,
3
,
1
,
7
]);
let
pubKeyBlob
=
{
data
:
pubKeyArray
};
let
priKeyBlob
=
{
data
:
priKeyArray
};
let
generator
=
cryptoFrame
W
ork
.
createAsyKeyGenerator
(
"
ECC256
"
);
let
generator
=
cryptoFrame
w
ork
.
createAsyKeyGenerator
(
"
ECC256
"
);
generator
.
convertKey
(
pubKeyBlob
,
priKeyBlob
,
(
error
,
data
)
=>
{
if
(
error
)
{
AlertDialog
.
show
({
message
:
"
Convert keypair fail
"
});
...
...
@@ -209,6 +208,94 @@ function testConvertAesKey() {
}
```
### 随机生成SM2密钥对,并获得二进制数据
示例6:随机生成非对称密钥KeyPair,并获得二进制数据(场景1、3)
1.
创建非对称密钥生成器。
2.
通过非对称密钥生成器随机生成非对称密钥。
3.
获取密钥对象的二进制数据。
以使用Promise方式随机生成SM2密钥(256位)为例:
```
js
import
cryptoFramework
from
'
@ohos.security.cryptoFramework
'
;
function
generateAsyKey
()
{
// 创建非对称密钥生成器
let
rsaGenerator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
SM2_256
"
);
// 通过非对称密钥生成器,随机生成非对称密钥
let
keyGenPromise
=
rsaGenerator
.
generateKeyPair
();
keyGenPromise
.
then
(
keyPair
=>
{
let
pubKey
=
keyPair
.
pubKey
;
let
priKey
=
keyPair
.
priKey
;
// 获取非对称密钥的二进制数据
let
pkBlob
=
pubKey
.
getEncoded
();
let
skBlob
=
priKey
.
getEncoded
();
AlertDialog
.
show
({
message
:
"
pk bin data
"
+
pkBlob
.
data
}
);
AlertDialog
.
show
({
message
:
"
sk bin data
"
+
skBlob
.
data
}
);
})
}
```
### 随机生成SM4密钥,并获得二进制数据
示例7:随机生成对称密钥SymKey,并获得二进制数据(场景1、3)
1.
创建对称密钥生成器。
2.
通过对称密钥生成器随机生成对称密钥。
3.
获取算法库密钥对象的二进制数据。
以使用Promise方式随机生成SM4密钥(128位)为例:
```
js
import
cryptoFramework
from
'
@ohos.security.cryptoFramework
'
;
// 字节流以16进制输出
function
uint8ArrayToShowStr
(
uint8Array
)
{
return
Array
.
prototype
.
map
.
call
(
uint8Array
,
(
x
)
=>
(
'
00
'
+
x
.
toString
(
16
)).
slice
(
-
2
))
.
join
(
''
);
}
function
testGenerateAesKey
()
{
// 创建对称密钥生成器
let
symKeyGenerator
=
cryptoFramework
.
createSymKeyGenerator
(
"
SM4_128
"
);
// 通过密钥生成器随机生成对称密钥
let
promiseSymKey
=
symKeyGenerator
.
generateSymKey
();
promiseSymKey
.
then
(
key
=>
{
// 获取对称密钥的二进制数据,输出长度为256bit的字节流
let
encodedKey
=
key
.
getEncoded
();
console
.
info
(
'
key hex:
'
+
uint8ArrayToShowStr
(
encodedKey
.
data
));
})
}
```
### 根据SM2密钥二进制数据,生成密钥对
示例8:根据指定的SM2非对称密钥二进制数据,生成KeyPair对象(场景2、3)
1.
获取SM2二进制密钥数据,封装成DataBlob对象。
2.
调用convertKey方法,传入公钥二进制和私钥二进制(二者非必选项,可只传入其中一个),转换为KeyPair对象。
```
js
import
cryptoFramework
from
'
@ohos.security.cryptoFramework
'
;
function
convertSM2AsyKey
()
{
let
pubKeyArray
=
new
Uint8Array
([
48
,
89
,
48
,
19
,
6
,
7
,
42
,
134
,
72
,
206
,
61
,
2
,
1
,
6
,
8
,
42
,
129
,
28
,
207
,
85
,
1
,
130
,
45
,
3
,
66
,
0
,
4
,
90
,
3
,
58
,
157
,
190
,
248
,
76
,
7
,
132
,
200
,
151
,
208
,
112
,
230
,
96
,
140
,
90
,
238
,
211
,
155
,
128
,
109
,
248
,
40
,
83
,
214
,
78
,
42
,
104
,
106
,
55
,
148
,
249
,
35
,
61
,
32
,
221
,
135
,
143
,
100
,
45
,
97
,
194
,
176
,
52
,
73
,
136
,
174
,
40
,
70
,
70
,
34
,
103
,
103
,
161
,
99
,
27
,
187
,
13
,
187
,
109
,
244
,
13
,
7
]);
let
priKeyArray
=
new
Uint8Array
([
48
,
49
,
2
,
1
,
1
,
4
,
32
,
54
,
41
,
239
,
240
,
63
,
188
,
134
,
113
,
31
,
102
,
149
,
203
,
245
,
89
,
15
,
15
,
47
,
202
,
170
,
60
,
38
,
154
,
28
,
169
,
189
,
100
,
251
,
76
,
112
,
223
,
156
,
159
,
160
,
10
,
6
,
8
,
42
,
129
,
28
,
207
,
85
,
1
,
130
,
45
]);
let
pubKeyBlob
=
{
data
:
pubKeyArray
};
let
priKeyBlob
=
{
data
:
priKeyArray
};
let
generator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
SM2_256
"
);
generator
.
convertKey
(
pubKeyBlob
,
priKeyBlob
,
(
error
,
data
)
=>
{
if
(
error
)
{
AlertDialog
.
show
({
message
:
"
Convert keypair fail
"
});
}
AlertDialog
.
show
({
message
:
"
Convert KeyPair success
"
});
})
}
```
## 非对称密钥对象根据参数生成与获取参数
### 场景说明
...
...
@@ -884,7 +971,7 @@ function stringToUint8Array(str) {
}
// 以Promise方式加密
function
encryptMessagePro
M
ise
()
{
function
encryptMessagePro
m
ise
()
{
// 生成非对称密钥生成器
let
rsaGenerator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
RSA1024|PRIMES_2
"
);
// 生成加解密生成器
...
...
@@ -927,7 +1014,7 @@ function encryptMessageCallback() {
}
// 以Promise方式加解密
function
decryptMessagePro
M
ise
()
{
function
decryptMessagePro
m
ise
()
{
// 生成非对称密钥生成器
let
rsaGenerator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
RSA1024|PRIMES_2
"
);
// 生成加解密生成器,用于加密
...
...
@@ -1211,6 +1298,220 @@ function rsaUseSpecDecryptOAEPPromise() {
}
```
### SM2加解密开发步骤
示例7:使用SM2非对称密钥的加解密操作
1.
生成SM2密钥。通过createAsyKeyGenerator接口创建AsyKeyGenerator对象,并生成SM2非对称密钥。
2.
生成Cipher对象。通过createCipher接口创建Cipher对象,执行初始化操作,设置密钥及加解密模式。
3.
执行加解密操作。通过调用Cipher对象提供的doFinal接口,执行加密操作生成密文或执行解密操作生成明文。
```
js
import
cryptoFramework
from
"
@ohos.security.cryptoFramework
"
let
plan
=
"
This is cipher test.
"
;
// 可理解的字符串转成字节流
function
stringToUint8Array
(
str
)
{
let
arr
=
[];
for
(
let
i
=
0
,
j
=
str
.
length
;
i
<
j
;
++
i
)
{
arr
.
push
(
str
.
charCodeAt
(
i
));
}
return
new
Uint8Array
(
arr
);
}
// 以Promise方式加密
function
encryptMessagePromise
()
{
// 生成非对称密钥生成器
let
sm2Generator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
SM2_256
"
);
// 生成加解密生成器
let
cipher
=
cryptoFramework
.
createCipher
(
"
SM2_256|SM3
"
);
// 通过非对称秘钥生成器生成非对称密钥对
let
keyGenPromise
=
sm2Generator
.
generateKeyPair
();
keyGenPromise
.
then
(
sm2KeyPair
=>
{
let
pubKey
=
sm2KeyPair
.
pubKey
;
// 初始化加解密操作环境:使用公钥开始加密
return
cipher
.
init
(
cryptoFramework
.
CryptoMode
.
ENCRYPT_MODE
,
pubKey
,
null
);
}).
then
(()
=>
{
// doFinal
let
input
=
{
data
:
stringToUint8Array
(
plan
)
};
return
cipher
.
doFinal
(
input
);
}).
then
(
dataBlob
=>
{
// 获取加密后的信息
console
.
info
(
"
EncryptOutPut is
"
+
dataBlob
.
data
);
});
}
// 以Callback方式加密
function
encryptMessageCallback
()
{
// 生成非对称密钥生成器
let
sm2Generator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
SM2_256
"
);
// 生成加解密生成器
let
cipher
=
cryptoFramework
.
createCipher
(
"
SM2_256|SM3
"
);
// 通过非对称秘钥生成器生成非对称密钥对
sm2Generator
.
generateKeyPair
(
function
(
err
,
keyPair
)
{
let
pubKey
=
keyPair
.
pubKey
;
// 初始化加解密操作环境:使用公钥开始加密
cipher
.
init
(
cryptoFramework
.
CryptoMode
.
ENCRYPT_MODE
,
pubKey
,
null
,
function
(
err
,
data
)
{
let
input
=
{
data
:
stringToUint8Array
(
plan
)
};
// doFinal
cipher
.
doFinal
(
input
,
function
(
err
,
data
)
{
// 获取加密后的信息
console
.
info
(
"
EncryptOutPut is
"
+
data
.
data
);
})
})
})
}
// 以Promise方式加解密
function
decryptMessagePromise
()
{
// 生成非对称密钥生成器
let
rsaGenerator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
SM2_256
"
);
// 生成加解密生成器,用于加密
let
cipher
=
cryptoFramework
.
createCipher
(
"
SM2_256|SM3
"
);
// 生成加解密生成器,用于解密
let
decoder
=
cryptoFramework
.
createCipher
(
"
SM2_256|SM3
"
);
// 通过非对称秘钥生成器生成非对称密钥对
let
keyGenPromise
=
rsaGenerator
.
generateKeyPair
();
let
keyPair
;
let
cipherDataBlob
;
let
input
=
{
data
:
stringToUint8Array
(
plan
)
};
keyGenPromise
.
then
(
rsaKeyPair
=>
{
keyPair
=
rsaKeyPair
;
// 初始化加解密操作环境:使用公钥开始加密
return
cipher
.
init
(
cryptoFramework
.
CryptoMode
.
ENCRYPT_MODE
,
keyPair
.
pubKey
,
null
);
}).
then
(()
=>
{
// 加密doFinal
return
cipher
.
doFinal
(
input
);
}).
then
(
dataBlob
=>
{
// 获取加密后的信息,并用于解密的入参
console
.
info
(
"
EncryptOutPut is
"
+
dataBlob
.
data
);
AlertDialog
.
show
({
message
:
"
output
"
+
dataBlob
.
data
});
cipherDataBlob
=
dataBlob
;
// 初始化加解密操作环境:使用私钥开始解密
return
decoder
.
init
(
cryptoFramework
.
CryptoMode
.
DECRYPT_MODE
,
keyPair
.
priKey
,
null
);
}).
then
(()
=>
{
// 解密doFinal
return
decoder
.
doFinal
(
cipherDataBlob
);
}).
then
(
decodeData
=>
{
// 验证解密后,数据与原先数据是否保持一致
if
(
decodeData
.
data
.
toString
()
===
input
.
data
.
toString
())
{
AlertDialog
.
show
({
message
:
"
decrypt success
"
});
return
;
}
AlertDialog
.
show
({
message
:
"
decrypt fail
"
});
});
}
// 以Callback方式加解密
function
decryptMessageCallback
()
{
// 生成非对称密钥生成器
let
rsaGenerator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
SM2_256
"
);
// 生成加解密生成器,用于加密
let
cipher
=
cryptoFramework
.
createCipher
(
"
SM2_256|SM3
"
);
// 生成加解密生成器,用于解密
let
decoder
=
cryptoFramework
.
createCipher
(
"
SM2_256|SM3
"
);
let
plainText
=
"
this is cipher text
"
;
let
input
=
{
data
:
stringToUint8Array
(
plainText
)
};
let
cipherData
;
let
keyPair
;
// 通过非对称秘钥生成器生成非对称密钥对
rsaGenerator
.
generateKeyPair
(
function
(
err
,
newKeyPair
)
{
keyPair
=
newKeyPair
;
// 初始化加解密操作环境:使用公钥开始加密
cipher
.
init
(
cryptoFramework
.
CryptoMode
.
ENCRYPT_MODE
,
keyPair
.
pubKey
,
null
,
function
(
err
,
data
)
{
// 加密doFinal
cipher
.
doFinal
(
input
,
function
(
err
,
data
)
{
// 获取加密后的信息,并用于解密的入参
AlertDialog
.
show
({
message
:
"
EncryptOutPut is
"
+
data
.
data
}
);
cipherData
=
data
;
// 初始化加解密操作环境:使用私钥开始解密
decoder
.
init
(
cryptoFramework
.
CryptoMode
.
DECRYPT_MODE
,
keyPair
.
priKey
,
null
,
function
(
err
,
data
)
{
// 解密doFinal
decoder
.
doFinal
(
cipherData
,
function
(
err
,
data
)
{
// 验证解密后,数据与原先数据是否保持一致
if
(
input
.
data
.
toString
()
===
data
.
data
.
toString
())
{
AlertDialog
.
show
({
message
:
"
decrype success
"
}
);
return
;
}
AlertDialog
.
show
({
message
:
"
decrype fail
"
}
);
});
});
});
});
});
}
```
### SM4 ECB以callback方式加解密开发步骤:
示例8:使用SM4对称密钥的加解密操作
1.
创建对称密钥生成器。
2.
通过已有二进制数据生成密钥。
3.
创建加解密生成器。
4.
通过加解密生成器加密或解密数据。
```
js
import
cryptoFramework
from
'
@ohos.security.cryptoFramework
'
;
function
stringToUint8Array
(
str
)
{
let
arr
=
[];
for
(
let
i
=
0
,
j
=
str
.
length
;
i
<
j
;
++
i
)
{
arr
.
push
(
str
.
charCodeAt
(
i
));
}
return
new
Uint8Array
(
arr
);
}
// 字节流转成可理解的字符串
function
uint8ArrayToString
(
array
)
{
let
arrayString
=
''
;
for
(
let
i
=
0
;
i
<
array
.
length
;
i
++
)
{
arrayString
+=
String
.
fromCharCode
(
array
[
i
]);
}
return
arrayString
;
}
// SM4 ECB模式示例,callback写法
function
testSM4Ecb
()
{
// 生成非对称密钥生成器
let
rsaGenerator
=
cryptoFramework
.
createSymKeyGenerator
(
'
SM4_128
'
);
// 生成加解密生成器,用于加密
let
cipher
=
cryptoFramework
.
createCipher
(
"
SM4_128|ECB|PKCS7
"
);
// 生成加解密生成器,用于解密
let
decoder
=
cryptoFramework
.
createCipher
(
"
SM4_128|ECB|PKCS7
"
);
let
plainText
=
"
this is cipher text
"
;
let
input
=
{
data
:
stringToUint8Array
(
plainText
)
};
let
cipherData
;
let
key
;
// 通过非对称秘钥生成器生成非对称密钥对
rsaGenerator
.
generateSymKey
(
function
(
err
,
newKey
)
{
key
=
newKey
;
// 初始化加解密操作环境:使用公钥开始加密
cipher
.
init
(
cryptoFramework
.
CryptoMode
.
ENCRYPT_MODE
,
key
,
null
,
function
(
err
,
data
)
{
// 加密doFinal
cipher
.
doFinal
(
input
,
function
(
err
,
data
)
{
// 获取加密后的信息,并用于解密的入参
AlertDialog
.
show
({
message
:
"
EncryptOutPut is
"
+
data
.
data
}
);
cipherData
=
data
;
// 初始化加解密操作环境:使用私钥开始解密
decoder
.
init
(
cryptoFramework
.
CryptoMode
.
DECRYPT_MODE
,
key
,
null
,
function
(
err
,
data
)
{
// 解密doFinal
decoder
.
doFinal
(
cipherData
,
function
(
err
,
data
)
{
// 验证解密后,数据与原先数据是否保持一致
if
(
input
.
data
.
toString
()
===
data
.
data
.
toString
())
{
AlertDialog
.
show
({
message
:
"
decrype success
"
}
);
return
;
}
AlertDialog
.
show
({
message
:
"
decrype fail
"
}
);
});
});
});
});
});
}
```
## 使用签名验签操作
### 场景说明
...
...
@@ -1219,6 +1520,7 @@ function rsaUseSpecDecryptOAEPPromise() {
1.
使用RSA签名验签操作
2.
使用ECC签名验签操作
3.
使用RSA签名验签,PSS模式时,获取、设置SignSpecItem参数。
4.
使用SM2签名验签操作
> **说明:**
>
...
...
@@ -1600,6 +1902,63 @@ function verifyMessageCallbackPSS() {
}
```
### SM2签名验签开发步骤
示例5:使用SM2操作
1.
生成SM2密钥。通过createAsyKeyGenerator接口创建AsyKeyGenerator对象,并生成SM2非对称密钥。
2.
生成Sign对象。通过createSign接口创建Sign对象,执行初始化操作并设置签名私钥。
3.
执行签名操作。通过Sign类提供的update接口,添加签名数据,并调用doFinal接口生成数据的签名。
4.
生成Verify对象。通过createVerify接口创建Verify对象,执行初始化操作并设置验签公钥。
5.
执行验签操作。通过Verify类提供的update接口,添加签名数据,并调用doFinal接口传入签名进行验签。
```
js
import
cryptoFramework
from
"
@ohos.security.cryptoFramework
"
// 可理解的字符串转成字节流
function
stringToUint8Array
(
str
)
{
var
arr
=
[];
for
(
var
i
=
0
,
j
=
str
.
length
;
i
<
j
;
++
i
)
{
arr
.
push
(
str
.
charCodeAt
(
i
));
}
var
tmpArray
=
new
Uint8Array
(
arr
);
return
tmpArray
;
}
let
globalKeyPair
;
let
SignMessageBlob
;
let
plan1
=
"
This is Sign test plan1
"
;
let
plan2
=
"
This is Sign test plan2
"
;
let
input1
=
{
data
:
stringToUint8Array
(
plan1
)
};
let
input2
=
{
data
:
stringToUint8Array
(
plan2
)
};
function
signAndVerify
()
{
let
rsaGenerator
=
cryptoFramework
.
createAsyKeyGenerator
(
"
SM2_256
"
);
let
signer
=
cryptoFramework
.
createSign
(
"
SM2_256|SM3
"
);
rsaGenerator
.
generateKeyPair
(
function
(
err
,
keyPair
)
{
globalKeyPair
=
keyPair
;
let
priKey
=
globalKeyPair
.
priKey
;
signer
.
init
(
priKey
,
function
(
err
,
data
)
{
signer
.
update
(
input1
,
function
(
err
,
data
)
{
signer
.
sign
(
input2
,
function
(
err
,
data
)
{
SignMessageBlob
=
data
;
console
.
info
(
"
sign output is
"
+
SignMessageBlob
.
data
);
let
verifyer
=
cryptoFramework
.
createVerify
(
"
SM2_256|SM3
"
);
verifyer
.
init
(
globalKeyPair
.
pubKey
,
function
(
err
,
data
)
{
verifyer
.
update
(
input1
,
function
(
err
,
data
)
{
verifyer
.
verify
(
input2
,
SignMessageBlob
,
function
(
err
,
data
)
{
console
.
info
(
"
verify result is
"
+
data
);
AlertDialog
.
show
({
message
:
"
decrype success
"
})
});
});
})
});
});
});
});
}
```
## 使用密钥协商操作
### 场景说明
...
...
zh-cn/application-dev/security/cryptoFramework-overview.md
浏览文件 @
491a711e
...
...
@@ -23,6 +23,10 @@
3DES,也称为 3DESede 或 TripleDES,是三重数据加密算法,相当于是对每个数据块应用三次DES的对称加密算法,它使用3个64位的密钥对数据块进行三次加密。相比DES,3DES因密钥长度变长,安全性有所提高,但其处理速度不高。因此又出现了AES加密算法,AES较于3DES速度更快、安全性更高。
-
**SM4密钥**
SM4密码算法是一个分组算法,该算法的分组长度为128位,密钥的长度为128位。加密算法与密钥扩展算法都采用32轮非线性迭代结构,数据解密和数据加密的算法结构相同,只是轮密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。
### 非对称密钥
非对称密钥使用公钥和私钥两个密钥来进行算法操作,公钥对外公开,私钥对外保密。对于加解密操作,一般使用公钥对明文进行加密形成密文,持有私钥的人即可对密文解密形成明文。对于签名验签操作,使用私钥对明文进行签名,公钥持有者可以通过公钥对签名数据做验签,验证数据是否被篡改。
...
...
@@ -77,6 +81,26 @@
pk:公钥,pk = (g ^ sk) mod p。
-
**SM2密钥**
SM2算法是一种基于椭圆曲线的公钥密码算法,其密钥长度为256位。SM2算法采用的是Fp域上的椭圆曲线。
Fp域下的SM2密钥参数,包括:
p: 大于3的素数,用于确定Fp。
a, b: 定义椭圆曲线的方程。
g: 椭圆曲线的一个基点(base point),可由gx,gy表示。
n: 基点g的阶(order)。
h: 余因子(cofactor)。
sk: 私钥,是一个随机整数,小于n。
pk: 公钥,是椭圆曲线上的一个点, pk = sk
*
g。
### 加解密
-
**对称AES加解密**
...
...
@@ -105,6 +129,19 @@
> ECB、CBC加密模式,明文长度不是64位整数倍,必须使用填充方法补足。<br/>
> 由于需要填充至分组大小,所以实际算法库中的PKCS5和PKCS7都是以分组大小作为填充长度的,即3DES加密填充至8字节。
-
**对称SM4加解密**
算法库目前提供了SM4加解密常用的6种加密模式:ECB、CBC、CTR、OFB、CFB和CFB128。SM4为分组加密算法,分组长度大小为128位。实际应用中明文最后一组可能不足128位,不足数据可以使用各种padding模式做数据填充。下文中描述了各个padding的区别:
-
NoPadding:不带填充。
-
PKCS5:填充字符由一个字节序列组成,每个字节填充该填充字节序列的长度,规定是8字节填充。
-
PKCS7:填充字符和PKCS5填充方法一致,但是可以在1-255字节之间任意填充。
> **说明:**
>
> ECB、CBC加密模式,明文长度不是128位整数倍,必须使用填充方法补足。<br/>
> 由于需要填充至分组大小,所以实际算法库中的PKCS5和PKCS7都是以分组大小作为填充长度的,即SM4加密填充至16字节。
-
**非对称RSA加解密**
RSA为块加密算法,加密长度需要在固定长度进行,实际应用中会使用各种padding模式做数据填充。算法库目前提供了RSA加解密常用的三种模式:NoPadding、PKCS1和PKCS1_OAEP。下文中描述了各个padding的区别:
...
...
@@ -121,6 +158,11 @@
>
> RSA钥模 = (RSA的bits + 7) / 8
-
**非对称SM2加解密**
SM2为块加密算法,加密长度需要在固定长度进行。OpenSSL中SM2算法加解密时使用零填充模式,如果数据长度不足32字节,则需要进行填充,填充的方式是在数据后面添加若干个0x00字节,直到数据长度达到32字节为止。
SM2加密过程中,首先需要生成一个随机数k,并使用公钥对kG进行加密,得到C1。然后,将明文数据进行SM3摘要计算,得到摘要值,并将摘要值与C1进行拼接,得到C3。接着,使用k和摘要值作为参数,对明文数据进行加密,得到C2。最后,将C1、C3、C2按顺序拼接起来,得到最终的加密结果。通过加入摘要值,增强了加密结果的安全性,同时也方便了解密过程中的验证。
### 签名验签
-
**RSA签名验签**
...
...
@@ -155,15 +197,16 @@
### 摘要
消息摘要
MD
算法是一种能将任意长度的输入消息,通过哈希算法生成长度固定的摘要的算法。消息摘要算法通过其不可逆的特性能被用于敏感信息的加密。消息摘要算法也被称为哈希算法或单向散列算法。
消息摘要算法是一种能将任意长度的输入消息,通过哈希算法生成长度固定的摘要的算法。消息摘要算法通过其不可逆的特性能被用于敏感信息的加密。消息摘要算法也被称为哈希算法或单向散列算法。
在摘要算法相同时,生成的摘要值主要有下列特点:
-
当输入消息相同时,生成摘要序列相同。
-
当输入消息的长度不一致时,生成摘要序列长度固定(摘要长度由算法决定)。
-
当输入消息不一致时,生成摘要序列几乎不会相同(依然存在相同概率,由摘要长度决定相同概率)。
消息摘要算法主要分为三类:MD,SHA与MAC(详见HMAC章节)。
MD算法包括MD2,MD4和MD5。
消息摘要算法主要分为两类:SHA和SM3。
SM3包括MD和MAC(详见HMAC章节)。
SM3中MD算法包括MD2,MD4和MD5。
SHA算法主要包括SHA1,SHA224,SHA256,SHA384,SHA512。
### 消息验证码
...
...
@@ -350,6 +393,39 @@ HMAC(Hash-based Message Authentication Code)是一种基于密钥的消息
> 1. DSA不支持通过指定私钥参数(p, q, g, sk)来生成私钥。
> 2. 当使用公共参数(p, q, g)来生成DSA密钥对时,DSA密钥长度至少需要1024位。
### SM2密钥生成规格
> **说明:**
>
> 从API version 10开始, 支持使用密钥参数来生成SM2密钥.
-
支持以字符串参数来生成SM2密钥,其生成参数如下表所示:
|非对称密钥算法|密钥长度(bit)|字符串参数|
|---|---|---|
|SM2|256|SM2_256|
> **说明:**
>
> “字符串参数”是“非对称密钥算法”和“密钥长度”使用连接符号“_”拼接而成,用于在创建非对称密钥生成器时,指定密钥规格。
### SM4密钥生成规格
> **说明:**
>
> 从API version 10开始, 支持使用密钥参数来生成SM4密钥.
-
支持以字符串参数来生成SM4密钥,其生成参数如下表所示:
|对称密钥算法|密钥长度(bit)|字符串参数|
|---|---|---|
|SM4|128|SM4_128|
> **说明:**
>
> “字符串参数”是“对称密钥算法”和“密钥长度”使用连接符号“_”拼接而成,用于在创建对称密钥生成器时,指定密钥规格。
## 加解密规格
### 对称加解密
...
...
@@ -372,11 +448,18 @@ HMAC(Hash-based Message Authentication Code)是一种基于密钥的消息
|AES|CFB|AES[128
\|
192
\|
256]
\|
CFB
\|
[NoPadding
\|
PKCS5
\|
PKCS7]|
|AES|GCM|AES[128
\|
192
\|
256]
\|
GCM
\|
[NoPadding
\|
PKCS5
\|
PKCS7]|
|AES|CCM|AES[128
\|
192
\|
256]
\|
CCM
\|
[NoPadding
\|
PKCS5
\|
PKCS7]|
|SM4|ECB|SM4_128
\|
ECB
\|
[NoPadding
\|
PKCS5
\|
PKCS7]|
|SM4|CBC|SM4_128
\|
CBC
\|
[NoPadding
\|
PKCS5
\|
PKCS7]|
|SM4|CTR|SM4_128
\|
CTR
\|
[NoPadding
\|
PKCS5
\|
PKCS7]|
|SM4|OFB|SM4_128
\|
OFB
\|
[NoPadding
\|
PKCS5
\|
PKCS7]|
|SM4|CFB|SM4_128
\|
CFB
\|
[NoPadding
\|
PKCS5
\|
PKCS7]|
|SM4|CFB128|SM4_128
\|
CFB128
\|
[NoPadding
\|
PKCS5
\|
PKCS7]|
> **说明:**
>
> 1. []中只能任选一项。
> 2. “字符串参数”是“对称加解密算法(含密钥长度)”、“分组模式”、“填充模式”拼接而成,用于在创建对称加解密实例时,指定对称加解密算法规格。
> 3. “字符串参数”中“SM4”和密钥长度间需要添加下划线
### 非对称RSA加解密
...
...
@@ -488,6 +571,24 @@ RSA加解密时,涉及三种填充模式:NoPadding, PKCS1和PKCS1_OAEP。
>
> 上表说明了算法库对于OAEP参数的获取和设置支持情况,打√的表示需要对该参数具有获取或设置的能力。
### 非对称SM2加解密
> **说明:**
>
> 从API version 10开始, 支持非对称SM2加解密不带密钥长度的规格。
SM2加解密时,仅支持C1C3C2密文排列组合模式。SM2非对称加密的结果由C1,C2,C3三部分组成。其中C1是根据生成的随机数计算出的椭圆曲线点,C2是密文数据,C3是通过指定摘要算法计算的值。国密新标准以C1,C3,C2顺序存放,不支持无摘要加密。
-
支持的加密算法:
| 非对称密钥类型 | 摘要 | 字符串参数 |
|---|---|---|
|SM2_256|[MD5
\|
SHA1
\|
SHA224
\|
SHA256
\|
SHA384
\|
SHA512
\|
SM3]|SM2_256
\|
[MD5
\|
SHA1
\|
SHA224
\|
SHA256
\|
SHA384
\|
SHA512
\|
SM3]|
> **说明:**
>
> “字符串参数”是“非对称密钥类型”、“填充模式”拼接而成,用于在创建非对称加解密实例时,指定非对称加解密算法规格。
## 签名验签规格
### RSA签名验签
...
...
@@ -628,6 +729,26 @@ RSA签名验签时,涉及两种填充模式:PKCS1和PSS。
> 例如:"DSA1024|SHA256"
> 3. 在上表最后一行,为了兼容由密钥参数生成的密钥,DSA签名验签参数输入密钥类型时支持不带长度,签名验签运算取决于实际输入的密钥长度。
### SM2签名验签
> **说明:**
>
> 从API version 10开始, 支持SM2签名验签规格。
-
支持的SM2参数:
|非对称密钥类型|摘要|字符串参数|
|---|---|---|
|SM2_256|SM3|SM2_256
\|
SM3|
|SM2|SM3|SM2
\|
SM3|
> **说明:**
>
> 1. []内的参数只能任选一项,非[]内的为固定值。
> 2. 使用时请从表格中选择非对称密钥类型、摘要二个数据,用|拼接成“字符串参数”,用于在创建非对称签名验签实例时,指定非对称签名验签算法规格。<br>
> SM2签名时只支持SM3摘要。
## 密钥协商规格
### ECDH
...
...
@@ -664,6 +785,7 @@ RSA签名验签时,涉及两种填充模式:PKCS1和PSS。
|HASH|SHA384|
|HASH|SHA512|
|HASH|MD5|
|HASH|SM3|
> **说明:**
>
...
...
@@ -680,6 +802,7 @@ RSA签名验签时,涉及两种填充模式:PKCS1和PSS。
|HASH|SHA256|
|HASH|SHA384|
|HASH|SHA512|
|HASH|SM3|
> **说明:**
>
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录