1. 摘要

本文主要列举实际测试的 mbedTLS 在各种配置参数下,SSL 握手的内存开销和相应的测试方法。

2. 测试方法

测试版本:ESP-IDF - tag: v2.1.1

测试思路是原子级的测试内存的开销,也就是说每次只要有内存分配就统计剩余的内存。代码修改方法如下:

  1. 找到 malloc的桩函数 _malloc_r,位于 esp-idf/components/newlib/syscalls.c
  • 在里面添加全局统计变量:
size_t s_mem_mark;
  1. 添加复位 API 和 获取 API:
size_t IRAM_ATTR get_mem_mark(void)
{
    return s_mem_mark;
}

void IRAM_ATTR reset_mem_mark(void)
{
    s_mem_mark = (size_t)-1;
}
  1. 修改 _malloc_r函数如下:
void* IRAM_ATTR _malloc_r(struct _reent *r, size_t size)
{
    void *p =heap_caps_malloc_default( size );

    if (p) {
        size_t mem_size = esp_get_free_heap_size();
        if (mem_size < s_mem_mark)
            s_mem_mark = mem_size;
    }

    return p;
}

测试代码代码修改如下:

  1. 找到 example/protocol/openssl_client/openssl_client_example_main.c,添加全局API 声明:

extern size_t get_mem_mark(void);
extern void reset_mem_mark(void);
extern size_t esp_get_free_heap_size(void);
```

  1. ESP_LOGI(TAG, "create SSL context ......"); 之前添加:

reset_mem_mark();
printf(“start heap %d\n”, esp_get_free_heap_size());

3. 在以下代码
ret = SSL_connect(ssl);
if (!ret) {
    ESP_LOGI(TAG, "failed " );
    goto failed4;
}
ESP_LOGI(TAG, "OK");
下方添加:
printf("min heap %d\n", get_mem_mark());
printf("end heap %d\n", esp_get_free_heap_size());

while (1);
SSL server 代码部分也做类似的修改。

通过以上的修改,可以统计出 SSL 握手之前的内存,握手中系统剩余的最小内存和握手结束以后的内存。为了测试方便可以用2个 ESP32 模组进行测试,1个做 server,1个做 client,通过 menuconfig 配置 WIFI 和连接参数进行测试。

# 3. 测试数据

本章主要列举各种配置参数和具体测试数据。

## 3.1 client 模式

本节具体列举了 `client` 模式下非认证,本地认证,双向认证模式下,配置各种大小的 `fragment` 和证书所消耗的内存。


### 3.1.1 非认证 Fragment 测试

测试非认证模式下各 ```fragment```大小和内存开销的关系,限定测试条件如下:

- RSA2048 加密
- 秘钥大小 1704 Bytes
- 证书大小 1261 Bytes


#### 3.1.1.1 数据

| Fragment/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 2048 | 234764 | 213140 | 222692 | 21624 | 12072 |
| 3072 | 234764 | 211404 | 220692 | 23360 | 14072 |
| 4096 | 234760 | 209288 | 218568 | 25472 | 16192 |
| 5120 | 234760 | 207248 | 216568 | 27512 | 18192 |
| 6144 | 234764 | 205284 | 214568 | 29480 | 20196 |
| 7168 | 234764 | 202844 | 212484 | 31920 | 22280 |
| 8192 | 234764 | 200796 | 210424 | 33968 | 24340 |


### 3.1.2 本地认证 Fragment 测试

测试本地认证模式下各 ```fragment```大小和内存开销的关系,限定测试条件如下:

- RSA2048 加密
- 秘钥大小 1704 Bytes
- 证书大小 1261 Bytes
- 认证证书大小 1261 Bytes


#### 3.1.2.1 数据

| Fragment/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 2048 | 234764 | 211432 | 220024 | 23332 | 14740 |
| 3072 | 234760 | 208700 | 217976 | 26060 | 16784 |
| 4096 | 234760 | 206644 | 215920 | 28116 | 18840 |
| 5120 | 234760 | 204600 | 213716 | 30160 | 21044 |
| 6144 | 234760 | 202560 | 211860 | 32200 | 22900 |
| 7168 | 234764 | 200520 | 209804 | 34244 | 24960 |
| 8192 | 234764 | 198496 | 207748 | 36268 | 27016 |


### 3.1.3 双向认证 Fragment 测试

测试双向认证模式下各 ```fragment```大小和内存开销的关系,限定测试条件如下:

- RSA2048 加密
- 秘钥大小 1704 Bytes
- 证书大小 1261 Bytes
- CA证书大小 1261 Bytes


#### 3.1.3.1 数据

| Fragment/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 2048 | 234764 | 208484 | 216452 | 26280 | 18312 |
| 3072 | 234764 | 205108 | 214404 | 29656 | 20360 |
| 4096 | 234760 | 202616 | 212012 | 32144 | 22748 |
| 5120 | 234764 | 201004 | 210304 | 33760 | 24460 |
| 6144 | 234760 | 198592 | 208192 | 36168 | 26568 |
| 7168 | 234764 | 196884 | 206172 | 37880 | 28592 |
| 8192 | 234760 | 194828 | 204096 | 39932 | 30664 |


### 3.1.4 非认证证书测试

测试非认证模式下各证书加秘钥总大小和内存开销的关系,限定测试条件如下:

- fragment 为 8KB


#### 3.1.4.1.数据

server 使用 RSA2048 — RSA8192 的秘钥和生成的证书。

| 证书和秘钥/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 2936 | 234764 | 208484 | 216452 | 26280 | 18312 |
| 4065 | 234764 | 205108 | 214404 | 29656 | 20360 |
| 5195 | 234760 | 202616 | 212012 | 32144 | 22748 |
| 6324 | 234764 | 201004 | 210304 | 33760 | 24460 |
| 7449 | 234760 | 198592 | 208192 | 36168 | 26568 |
| 8575 | 234764 | 196884 | 206172 | 37880 | 28592 |
| 9709 | 234760 | 194828 | 204096 | 39932 | 30664 |


### 3.1.5 单向认证证书测试

测试单向认证模式下各证书加秘钥总大小和内存开销的关系,限定测试条件如下:

- fragment 为 8KB

#### 3.1.5.1 数据

server 使用 RSA2048 - RSA8192 的秘钥和生成的证书。

| 证书和秘钥/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 4197 | 234564 | 198148 | 207760 | 36416 | 26804 |
| 5679 | 234760 | 195028 | 203772 | 39732 | 30988 |
| 7155 | 234564 | 192028 | 202200 | 42536 | 32364 |
| 8633 | 234760 | 188760 | 200552 | 46000 | 34208 |
| 10103 | 234760 | 186336 | 198772 | 48424 | 35988 |
| 11575 | 234760 | 184000 | 197228 | 50760 | 37532 |
| 13058 | 234760 | 179832 | 195572 | 54928 | 39188 |


### 3.1.6 双向认证证书测试

测试双向认证模式下各证书加秘钥大小和内存开销的关系,限定测试条件如下:

- fragment 为 8KB


#### 3.1.6.1 数据

server 使用 RSA2048 — RSA8192 的秘钥和生成的证书,以下表格首项为 client 和 server 端认证证书,证书和秘钥的总和。

| 证书和秘钥/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 8394 | 234760 | 194816 | 204104 | 39944 | 30656 |
| 11358 | 234760 | 189988 | 199072 | 44772 | 35688 |
| 14310 | 234760 | 188208 | 196672 | 46552 | 38088 |
| 17266 | 234760 | 183472 | 194060 | 51288 | 40700 |
| 20206 | 234760 | 178840 | 191296 | 55920 | 43464 |
| 23150 | 234760 | 173740 | 188488 | 61020 | 46272 |
| 26116 | 234760 | 170400 | 186056 | 64360 | 48704 |


## 3.2 server 模式

本节具体列举了 server 模式下非认证模式,双向认证模式和配置各种大小的证书所消耗的内存。


### 3.2.1 非认证 Fragment 测试

测试非认证模式下各 ```fragment```大小和内存开销的关系,限定测试条件如下:

- RSA2048 加密
- 秘钥大小 1704 Bytes
- 证书大小 1261 Bytes

####  3.2.1.1 数据

| Fragment/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 2048 | 234780 | 202724 | 216284 | 32056 | 18496 |
| 3072 | 234780 | 200691 | 214271 | 34089 | 20509 |
| 4096 | 234780 | 198659 | 212261 | 36121 | 22519 |
| 5120 | 234780 | 196628 | 210251 | 38152 | 24529 |
| 6144 | 234780 | 194597 | 208241 | 40183 | 26539 |
| 7168 | 234780 | 192565 | 206231 | 42215 | 28549 |
| 8192 | 234780 | 190536 | 204224 | 44244 | 30556 |


### 3.2.2 双向认证 Fragment 测试

测试双向认证模式下各 ```fragment```大小和内存开销的关系,限定测试条件如下:

- RSA2048 加密
- 秘钥大小 1704 Bytes
- 证书大小 1261 Bytes


#### 3.2.2.1 数据

| Fragment/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 2048 | 234964 | 199148 | 211048 | 35816 | 23916 |
| 3072 | 234964 | 196693 | 208964 | 38271 | 26000 |
| 4096 | 234964 | 194241 | 206882 | 40723 | 28082 |
| 5120 | 234964 | 191789 | 204801 | 43175 | 30163 |
| 6144 | 234964 | 189337 | 202720 | 45627 | 32244 |
| 7168 | 234964 | 186885 | 200638 | 48079 | 34326 |
| 8192 | 234964 | 184436 | 198560 | 50528 | 36404 |


### 3.2.3 非认证证书测试

测试非认证模式下各证书加秘钥大小和内存开销的关系,限定测试条件如下:

- fragment 为 8KB

#### 3.2.3.1 数据

server 使用 RSA2048 — RSA8192 的秘钥和生成的证书。,以下首项为 server 端证书和秘钥的总和。

| 证书和秘钥/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 2965 | 234780 | 190536 | 204224 | 44244 | 30556 |
| 4097 | 234780 | 186153 | 202446 | 48631 | 32334 |
| 5229 | 234780 | 181767 | 200672 | 53017 | 34108 |
| 6361 | 234780 | 177380 | 198897 | 57404 | 35883 |
| 7493 | 234780 | 172994 | 197122 | 61790 | 37658 |
| 8625 | 234780 | 168607 | 195347 | 66177 | 39433 |
| 9704 | 234780 | 164356 | 193632 | 70424 | 41148 |


### 3.2.4 双向认证证书测试

测试双向认证模式下各证书加秘钥大小和内存开销的关系,限定测试条件如下:

- fragment 为 8KB


#### 3.2.4.1 数据

server 使用 RSA2048 — RSA8192 的秘钥和生成的证书。,以下首项为 server 端证书和秘钥的总和。

| 证书和秘钥/B | 开始 | 最小 | 最大 | 最大消耗/B | 最后消耗/B |
| --- | --- | --- | --- | --- | --- |
| 4226 | 234964 | 184436 | 198560 | 50528 | 36404 |
| 5705 | 234964 | 180291 | 195530 | 54677 | 39434 |
| 7184 | 234964 | 176147 | 192498 | 58821 | 42466 |
| 8663 | 234964 | 172002 | 189465 | 62966 | 45499 |
| 10142 | 234964 | 167858 | 186432 | 67110 | 48532 |
| 11621 | 234964 | 163713 | 183399 | 71255 | 51565 |
| 13053 | 234964 | 159700 | 180460 | 75264 | 54504 |

# 4. 帮助

- ```cert.sh```用于生成测试使用的证书,```openssl.cnf```为生成证书的配置文件,可以通过修改 ```cert.sh```中的全局变量 ```KEY_BITS```来起到修改证书的大小的作用,直接修改其他参数也能实现,但是感觉意义不大

- 如果手头只有1个 ESP32 模组,可以使用 openssl 命令来创建 client 和 server 进行测试,参考链接如下:
  
  client: 

  server: 

#5. 附件
**cert.sh**

#!/bin/bash

SAVEIFS=dmesg内存占用统计 mbedtls消耗内存_Fragment(echo -en “\n\b”)

ROOT_SUBJECT="/C=C1/ST=JS1/L=WX1/O=ESP1/OU=ESP1/CN=Server1 CA/emailAddress=ESP1"
LEVEL2_SUBJECT="/C=C2/ST=JS22/L=WX22/O=ESP22/OU=ESP22/CN=Server22 CA/emailAddress=ESP22"
LEVEL3_SUBJECT="/C=C3/ST=JS333/L=WX333/O=ESP333/OU=ESP333/CN=Server333 CA/emailAddress=ESP333"

SERVER_CERT_NAME=“RootCA.crt”
SERVER_KEY_NAME=“root-key.key”

CLIENT_CERT_NAME=“RootCA.crt”
CLIENT_KEY_NAME=“root-key.key”

KEY_BITS=“4096”

echo “create root CA key”
openssl genrsa -out root-key.key $KEY_BITS
echo ----------------------
echo “create root cert request”
openssl req -new -key root-key.key -out root-req.csr -text -subj $ROOT_SUBJECT
echo ----------------------
echo “create root self sign cert”
openssl x509 -req -in root-req.csr -out RootCA.crt -sha1 -signkey root-key.key -days 3650 -text -extfile openssl.cnf -extensions v3_ca

echo “create 2 level cert key”
openssl genrsa -out root-mid.key $KEY_BITS
echo ----------------------
echo “create 2 level cert csr”
openssl req -new -key root-mid.key -out root-mid.csr -text -subj $LEVEL2_SUBJECT
echo ----------------------
echo “sign with root-crt”
openssl x509 -req -in root-mid.csr -CA RootCA.crt -CAkey root-key.key -CAcreateserial -days 3650 -out RootMid.crt -text -extfile openssl.cnf -extensions v3_ca

echo “create 3 level cert key”
openssl genrsa -out server.key $KEY_BITS
echo “create 3 level cert csr”
openssl req -new -key server.key -out server.csr -text -subj $LEVEL3_SUBJECT
echo “sign with level2 cert-crt”
openssl x509 -req -in server.csr -CA RootMid.crt -CAkey root-mid.key -CAcreateserial -days 3560 -out Server.crt -text -extfile openssl.cnf -extensions v3_ca

echo “”
echo ----------------------
echo "server uses certification " $SERVER_CERT_NAME " and key " $SERVER_KEY_NAME
echo "client uses certification " $CLIENT_CERT_NAME " and key " $CLIENT_KEY_NAME

rm *.csr *.srl

IFS=$SAVEIFS

**openssl.cnf**

################################################################

openssl example configuration file.

This is mostly used for generation of certificate requests.

#################################################################
[ ca ]
default_ca= CA_default # The default ca section
#################################################################

[ CA_default ]

dir=~/tmp/cert # Where everything is kept
certs=$dir # Where the issued certs are kept
crl_dir= $dir/crl # Where the issued crl are kept
database= $dir/index.txt # database index file
new_certs_dir= KaTeX parse error: Expected 'EOF', got '#' at position 18: …r/new_certs #̲ default place …dir/CA/OrbixCA # The CA certificate
serial= $dir/serial # The current serial number
crl= $dir/crl.pem # The current CRL
private_key= $dir/CA/OrbixCA.pk # The private key
RANDFILE= $dir/.rand # private random number file
default_days= 365 # how long to certify for
default_crl_days= 30 # how long before next CRL
default_md= md5 # which message digest to use
preserve= no # keep passed DN ordering

A few different ways of specifying how closely the request should

conform to the details of the CA

policy= policy_match # For the CA policy

[ policy_match ]
countryName= match
stateOrProvinceName= match
organizationName= match
organizationalUnitName= optional
commonName= supplied
emailAddress= optional

For the `anything’ policy

At this point in time, you must list all acceptable `object’

types

[ policy_anything ]
countryName = optional
stateOrProvinceName= optional
localityName= optional
organizationName = optional
organizationalUnitName = optional
commonName= supplied
emailAddress= optional

[ req ]
default_bits = 1024
default_keyfile= privkey.pem
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca

[ req_distinguished_name ]
countryName= Country Name (2 letter code)
countryName_min= 2
countryName_max = 2
stateOrProvinceName= State or Province Name (full name)
localityName = Locality Name (eg, city)
organizationName = Organization Name (eg, company)
organizationalUnitName = Organizational Unit Name (eg, section)
commonName = Common Name (eg. YOUR name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 40

[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 20
unstructuredName= An optional company name

[ v3_ca ]
basicConstraints = CA:true