Skip to content

Commit 3c9a75f

Browse files
aerlondevyte
andauthored
Add CryptoInterface library (#6961)
* - Add CryptoInterface library. - Add TypeConversion core files. * Fix compiler errors. - Make HelloCrypto.ino stylish. - Include assert.h in CryptoInterface.cpp. * - Move base36 arrays to PROGMEM in TypeConversionFunctions.cpp. - Add deprecated attribute to SHA1 and MD5 hashes. - Remove _warningsEnabled since this has been replaced by the deprecated attribute. - Prefix all getters with "get". - Move all CryptoInterface functionality to the experimental namespace. - Change formatting of core files. - Improve comments. * - Update keywords.txt. * - Remove WiFi.disconnect() from setup() in HelloCrypto example since it no longer seems to be required. * - Classify everything. - Remove delay in setup() from HelloCrypto example since it does not seem to be required to prevent missing initial Serial prints. - Mark type conversion functions as big endian. - Update keywords.txt. * - Remove namespace experimental. - Create ESP.random functions in the core based on the defaultNonceGenerator code, and use these in defaultNonceGenerator. - Rename CryptoInterface to esp8266::Crypto and move all functionality to the core. - Remove need to #include <bearssl/bearssl.h> in the Crypto header file by changing br_hkdf_context to ::br_hkdf_context. - Restyle code files for core usage. * - Re-add namespace experimental. - Improve comments. * - Remove namespace esp8266. - Rename namespace Crypto to namespace crypto. Co-authored-by: Anders <andlo151@student.liu.se> Co-authored-by: Develo <deveyes@gmail.com>
1 parent ec76442 commit 3c9a75f

File tree

9 files changed

+1757
-1
lines changed

9 files changed

+1757
-1
lines changed

Diff for: cores/esp8266/Crypto.cpp

+552
Large diffs are not rendered by default.

Diff for: cores/esp8266/Crypto.h

+845
Large diffs are not rendered by default.

Diff for: cores/esp8266/Esp.cpp

+57
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,63 @@ bool EspClass::eraseConfig(void) {
522522
return true;
523523
}
524524

525+
uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) const
526+
{
527+
/**
528+
* The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266):
529+
*
530+
* "When used correctly, every 32-bit value the system reads from the RNG_DATA_REG register of the random number generator is a true random number.
531+
* These true random numbers are generated based on the noise in the Wi-Fi/BT RF system.
532+
* When Wi-Fi and BT are disabled, the random number generator will give out pseudo-random numbers.
533+
*
534+
* When Wi-Fi or BT is enabled, the random number generator is fed two bits of entropy every APB clock cycle (normally 80 MHz).
535+
* Thus, for the maximum amount of entropy, it is advisable to read the random register at a maximum rate of 5 MHz.
536+
* A data sample of 2 GB, read from the random number generator with Wi-Fi enabled and the random register read at 5 MHz,
537+
* has been tested using the Dieharder Random Number Testsuite (version 3.31.1).
538+
* The sample passed all tests."
539+
*
540+
* Since ESP32 is the sequal to ESP8266 it is unlikely that the ESP8266 is able to generate random numbers more quickly than 5 MHz when run at a 80 MHz frequency.
541+
* A maximum random number frequency of 0.5 MHz is used here to leave some margin for possibly inferior components in the ESP8266.
542+
* It should be noted that the ESP8266 has no Bluetooth functionality, so turning the WiFi off is likely to cause RANDOM_REG32 to use pseudo-random numbers.
543+
*
544+
* It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available.
545+
* However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during nonce generation.
546+
* Thus only delayMicroseconds() is used below.
547+
*/
548+
549+
constexpr uint8_t cooldownMicros = 2;
550+
static uint32_t lastCalledMicros = micros() - cooldownMicros;
551+
552+
uint32_t randomNumber = 0;
553+
554+
for(size_t byteIndex = 0; byteIndex < outputSizeBytes; ++byteIndex)
555+
{
556+
if(byteIndex % 4 == 0)
557+
{
558+
// Old random number has been used up (random number could be exactly 0, so we can't check for that)
559+
560+
uint32_t timeSinceLastCall = micros() - lastCalledMicros;
561+
if(timeSinceLastCall < cooldownMicros)
562+
delayMicroseconds(cooldownMicros - timeSinceLastCall);
563+
564+
randomNumber = RANDOM_REG32;
565+
lastCalledMicros = micros();
566+
}
567+
568+
resultArray[byteIndex] = randomNumber;
569+
randomNumber >>= 8;
570+
}
571+
572+
return resultArray;
573+
}
574+
575+
uint32_t EspClass::random() const
576+
{
577+
union { uint32_t b32; uint8_t b8[4]; } result;
578+
random(result.b8, 4);
579+
return result.b32;
580+
}
581+
525582
uint32_t EspClass::getSketchSize() {
526583
static uint32_t result = 0;
527584
if (result)

Diff for: cores/esp8266/Esp.h

+3
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ class EspClass {
164164

165165
bool eraseConfig();
166166

167+
uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const;
168+
uint32_t random() const;
169+
167170
#ifndef CORE_MOCK
168171
inline uint32_t getCycleCount() __attribute__((always_inline));
169172
#else

Diff for: cores/esp8266/TypeConversion.cpp

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
TypeConversion functionality
3+
Copyright (C) 2019 Anders Löfgren
4+
5+
License (MIT license):
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in
15+
all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
THE SOFTWARE.
24+
*/
25+
26+
#include <assert.h>
27+
#include "TypeConversion.h"
28+
29+
namespace esp8266
30+
{
31+
namespace TypeConversion
32+
{
33+
const char base36Chars[36] PROGMEM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
34+
const uint8_t base36CharValues[75] PROGMEM {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, // 0 to 9
35+
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, // Upper case letters
36+
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 // Lower case letters
37+
};
38+
39+
40+
String uint8ArrayToHexString(const uint8_t *uint8Array, const uint32_t arrayLength)
41+
{
42+
String hexString;
43+
if (!hexString.reserve(2 * arrayLength)) // Each uint8_t will become two characters (00 to FF)
44+
{
45+
return emptyString;
46+
}
47+
48+
for (uint32_t i = 0; i < arrayLength; ++i)
49+
{
50+
hexString += (char)pgm_read_byte(base36Chars + (uint8Array[i] >> 4));
51+
hexString += (char)pgm_read_byte(base36Chars + uint8Array[i] % 16);
52+
}
53+
54+
return hexString;
55+
}
56+
57+
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength)
58+
{
59+
assert(hexString.length() >= arrayLength * 2); // Each array element can hold two hexString characters
60+
61+
for (uint32_t i = 0; i < arrayLength; ++i)
62+
{
63+
uint8Array[i] = (pgm_read_byte(base36CharValues + hexString.charAt(i * 2) - '0') << 4) + pgm_read_byte(base36CharValues + hexString.charAt(i * 2 + 1) - '0');
64+
}
65+
66+
return uint8Array;
67+
}
68+
69+
uint8_t *uint64ToUint8ArrayBE(const uint64_t value, uint8_t *resultArray)
70+
{
71+
resultArray[7] = value;
72+
resultArray[6] = value >> 8;
73+
resultArray[5] = value >> 16;
74+
resultArray[4] = value >> 24;
75+
resultArray[3] = value >> 32;
76+
resultArray[2] = value >> 40;
77+
resultArray[1] = value >> 48;
78+
resultArray[0] = value >> 56;
79+
80+
return resultArray;
81+
}
82+
83+
uint64_t uint8ArrayToUint64BE(const uint8_t *inputArray)
84+
{
85+
uint64_t result = (uint64_t)inputArray[0] << 56 | (uint64_t)inputArray[1] << 48 | (uint64_t)inputArray[2] << 40 | (uint64_t)inputArray[3] << 32
86+
| (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7];
87+
88+
return result;
89+
}
90+
}
91+
}

Diff for: cores/esp8266/TypeConversion.h

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
TypeConversion functionality
3+
Copyright (C) 2019 Anders Löfgren
4+
5+
License (MIT license):
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in
15+
all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
THE SOFTWARE.
24+
*/
25+
26+
#ifndef __ESP8266_TYPECONVERSION_H__
27+
#define __ESP8266_TYPECONVERSION_H__
28+
29+
#include <Arduino.h>
30+
31+
namespace esp8266
32+
{
33+
namespace TypeConversion
34+
{
35+
extern const char base36Chars[36];
36+
37+
// Subtract '0' to normalize the char before lookup.
38+
extern const uint8_t base36CharValues[75];
39+
40+
/**
41+
Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array.
42+
All array elements will be padded with zeroes to ensure they are converted to 2 String characters each.
43+
44+
@param uint8Array The array to make into a HEX String.
45+
@param arrayLength The size of uint8Array, in bytes.
46+
@return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed.
47+
*/
48+
String uint8ArrayToHexString(const uint8_t *uint8Array, const uint32_t arrayLength);
49+
50+
/**
51+
Convert the contents of a String in HEX format to a uint8_t array. Index 0 of the array will represent the start of the String.
52+
There must be 2 String characters for each array element. Use padding with zeroes where required.
53+
54+
@param hexString The HEX String to convert to a uint8_t array. Must contain at least 2*arrayLength characters.
55+
@param uint8Array The array to fill with the contents of the hexString.
56+
@param arrayLength The number of bytes to fill in uint8Array.
57+
@return A pointer to the uint8Array.
58+
*/
59+
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength);
60+
61+
/**
62+
Takes a uint64_t value and stores the bits in a uint8_t array. Assumes index 0 of the array should contain MSB (big endian).
63+
64+
@param value The uint64_t value to convert to a uint8_t array.
65+
@param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes.
66+
@return The resultArray.
67+
*/
68+
uint8_t *uint64ToUint8ArrayBE(const uint64_t value, uint8_t *resultArray);
69+
70+
/**
71+
Takes a uint8_t array and converts the first 8 (lowest index) elements to a uint64_t. Assumes index 0 of the array contains MSB (big endian).
72+
73+
@param inputArray A uint8_t array containing the data to convert to a uint64_t. Should have a size of at least 8 bytes.
74+
@return A uint64_t representation of the first 8 bytes of the array.
75+
*/
76+
uint64_t uint8ArrayToUint64BE(const uint8_t *inputArray);
77+
}
78+
}
79+
80+
#endif
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
This example demonstrates the usage of the ESP8266 Crypto implementation, which aims to contain easy-to-use cryptographic functions.
3+
Crypto is currently primarily a frontend for the cryptographic library BearSSL which is used by `BearSSL::WiFiClientSecure` and `BearSSL::WiFiServerSecure` in the ESP8266 Arduino Core.
4+
Extensive documentation can be found in the Crypto source code files and on the [BearSSL homepage](https://www.bearssl.org).
5+
*/
6+
7+
#include <ESP8266WiFi.h>
8+
#include <TypeConversion.h>
9+
#include <Crypto.h>
10+
11+
namespace TypeCast = esp8266::TypeConversion;
12+
using namespace experimental;
13+
14+
/**
15+
NOTE: Although we could define the strings below as normal String variables,
16+
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
17+
The reason is that this approach will place the strings in flash memory which will help save RAM during program execution.
18+
Reading strings from flash will be slower than reading them from RAM,
19+
but this will be a negligible difference when printing them to Serial.
20+
21+
More on F(), FPSTR() and PROGMEM:
22+
https://github.com/esp8266/Arduino/issues/1143
23+
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
24+
*/
25+
constexpr char masterKey[] PROGMEM = "w86vn@rpfA O+S"; // Use 8 random characters or more
26+
27+
void setup() {
28+
// Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 .
29+
// This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to.
30+
WiFi.persistent(false);
31+
32+
Serial.begin(115200);
33+
34+
Serial.println();
35+
Serial.println();
36+
}
37+
38+
void loop() {
39+
// This serves only to demonstrate the library use. See the header file for a full list of functions.
40+
41+
String exampleData = F("Hello Crypto World!");
42+
Serial.println(String(F("This is our example data: ")) + exampleData);
43+
44+
uint8_t resultArray[crypto::SHA256::NATURAL_LENGTH] { 0 };
45+
uint8_t derivedKey[crypto::ENCRYPTION_KEY_LENGTH] { 0 };
46+
47+
static uint32_t encryptionCounter = 0;
48+
49+
50+
// Generate the salt to use for HKDF
51+
uint8_t hkdfSalt[16] { 0 };
52+
crypto::getNonceGenerator()(hkdfSalt, sizeof hkdfSalt);
53+
54+
// Generate the key to use for HMAC and encryption
55+
crypto::HKDF hkdfInstance(FPSTR(masterKey), (sizeof masterKey) - 1, hkdfSalt, sizeof hkdfSalt); // (sizeof masterKey) - 1 removes the terminating null value of the c-string
56+
hkdfInstance.produce(derivedKey, sizeof derivedKey);
57+
58+
// Hash
59+
crypto::SHA256::hash(exampleData.c_str(), exampleData.length(), resultArray);
60+
Serial.println(String(F("\nThis is the SHA256 hash of our example data, in HEX format:\n")) + TypeCast::uint8ArrayToHexString(resultArray, sizeof resultArray));
61+
Serial.println(String(F("This is the SHA256 hash of our example data, in HEX format, using String output:\n")) + crypto::SHA256::hash(exampleData));
62+
63+
64+
// HMAC
65+
// Note that HMAC output length is limited
66+
crypto::SHA256::hmac(exampleData.c_str(), exampleData.length(), derivedKey, sizeof derivedKey, resultArray, sizeof resultArray);
67+
Serial.println(String(F("\nThis is the SHA256 HMAC of our example data, in HEX format:\n")) + TypeCast::uint8ArrayToHexString(resultArray, sizeof resultArray));
68+
Serial.println(String(F("This is the SHA256 HMAC of our example data, in HEX format, using String output:\n")) + crypto::SHA256::hmac(exampleData, derivedKey, sizeof derivedKey, crypto::SHA256::NATURAL_LENGTH));
69+
70+
71+
// Authenticated Encryption with Associated Data (AEAD)
72+
String dataToEncrypt = F("This data is not encrypted.");
73+
uint8_t resultingNonce[12] { 0 }; // The nonce is always 12 bytes
74+
uint8_t resultingTag[16] { 0 }; // The tag is always 16 bytes
75+
76+
Serial.println(String(F("\nThis is the data to encrypt: ")) + dataToEncrypt);
77+
78+
// Note that the key must be ENCRYPTION_KEY_LENGTH long.
79+
crypto::ChaCha20Poly1305::encrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag);
80+
Serial.println(String(F("Encrypted data: ")) + dataToEncrypt);
81+
82+
bool decryptionSucceeded = crypto::ChaCha20Poly1305::decrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag);
83+
encryptionCounter++;
84+
85+
if (decryptionSucceeded) {
86+
Serial.print(F("Decryption succeeded. Result: "));
87+
} else {
88+
Serial.print(F("Decryption failed. Result: "));
89+
}
90+
91+
Serial.println(dataToEncrypt);
92+
93+
94+
Serial.println(F("\n##########################################################################################################\n"));
95+
96+
delay(10000);
97+
}

Diff for: libraries/esp8266/keywords.txt

+31
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@
1212

1313
ESP KEYWORD1
1414

15+
crypto KEYWORD1
16+
nonceGeneratorType KEYWORD1
17+
MD5 KEYWORD1
18+
SHA1 KEYWORD1
19+
SHA224 KEYWORD1
20+
SHA256 KEYWORD1
21+
SHA384 KEYWORD1
22+
SHA512 KEYWORD1
23+
MD5SHA1 KEYWORD1
24+
HKDF KEYWORD1
25+
ChaCha20Poly1305 KEYWORD1
26+
1527
#######################################
1628
# Methods and Functions (KEYWORD2)
1729
#######################################
@@ -60,6 +72,21 @@ getResetInfo KEYWORD2
6072
getResetInfoPtr KEYWORD2
6173
eraseConfig KEYWORD2
6274
getCycleCount KEYWORD2
75+
random->KEYWORD2
76+
77+
setCtMinDataLength KEYWORD2
78+
getCtMinDataLength KEYWORD2
79+
setCtMaxDataLength KEYWORD2
80+
getCtMaxDataLength KEYWORD2
81+
setNonceGenerator KEYWORD2
82+
getNonceGenerator KEYWORD2
83+
hash KEYWORD2
84+
hmac KEYWORD2
85+
hmacCT KEYWORD2
86+
init KEYWORD2
87+
produce KEYWORD2
88+
encrypt KEYWORD2
89+
decrypt KEYWORD2
6390

6491
#######################################
6592
# Constants (LITERAL1)
@@ -79,6 +106,10 @@ WAKE_RF_DISABLED LITERAL1
79106
ADC_VCC LITERAL1
80107
ADC_TOUT LITERAL1
81108

109+
NATURAL_LENGTH LITERAL1
110+
ENCRYPTION_KEY_LENGTH LITERAL1
111+
CT_MAX_DIFF LITERAL1
112+
82113
#######################################
83114
# namespace esp8266
84115
#######################################

0 commit comments

Comments
 (0)