mbedcrypto
is a portable, small, easy to use, feature rich and fast
c++11/14
library for cryptography based on fantastic and clean
mbedtlsnote
note.
a sister project for Qt5
developers is available as
qpolarssl, also mbedcrypto
is newer
and has more features with smaller footprint.
- small size: the
mbedcrypto
is less than250KB
in size (stripped under Linux and OS X) with all predefined algorithms. it can be easily embedded into your service or application. - easy to use: although cryptographynote is
complex and complicated,
mbedcrypto
hides most of the complexities, tries to be easy to use and hard to misuse. see samples - portable: needs an standard
c++11/14
compiler and compliantstl
, compiled and tested by:gcc 5.x+
underlinux
clang 3.6+
underos x
msvc 2015
community edition underwindows 7
- low dependency:
- high+low level: both high level (c++ objects / exception) and low level (c pointer / error code) functions are available. see samples
- highly configurable: to add or remove the algorithms, simply change
cmake
build options. see build options
following algorithms are included in mbedcrypto
in default build (see
samples):
-
binary/text conversions: see samples
hex
base64
-
hashes (message digest): see samples
md5
sha1
sha224
/sha256
sha384
/sha512
hmac
- optional hashes:
ripemd160
,md4
,md2
(deprecated)
-
ciphers (symmetric): see samples
aes
(128, 192, 256 bits) andaes-ni
(hardware accelerated)des
and3des
(triple-des)- optional ciphers:
blowfish
,camellia
andarc4
-
cipher block modes:
ecb
electronic codebookcbc
cipher block chainingctr
counter modegcm
Galois/counter andccm
(counter cbc-mac) modes. see authneticated encryption with additional data (AEAD)- optional block modes:
cfb
,stream
(forarc4
)
-
paddings:
pkcs7
- one and zeros
- zeros and length
- zeros
-
random byte generator: see samples
ctr_drbg
counter mode deterministic random byte generator based onaes-256
NIST SP 800-90
-
pki (asymmetric): public key infrastructure, see samples
rsa
pem
andder
key formats (ASN.1)- optional pks:
eckey
elliptic curve,eckey_dh
elliptic key Diffie–Hellman,ecdsa
elliptic key digital signature algorithm,rsa_alt
andrsassa_pss
RSA standard signature algorithm, probabilistic signature scheme - optional
rsa
key generator - optional
ec curves
from well known domain parameters asNIST
,Kolbitz
,brainpool
andCurve25519
.
total number of supported algorithms:
- hashes: 9
- paddings: 5
- ciphers: 47
- pki: 6
see types.hpp
after cloning this repository, first update the dependencies:
# on mbedcrypto directory
$medcrypto/> ./update-dependencies.sh
this script automatically setups 3rdparty
directory, then tries to pull or
update dependencies from github.
under Linux, OS X:
$medcrypto/> mkdir build
$medcrypto/> cd build
$build/> cmake ..
$build/> make
the
mbedcrypto
library and the companion unit test app would be built onxbin
directory.
under Windows (MSVC) probably:
$medcrypto/> mkdir build
$medcrypto/> cd build
$build/> cmake ..
$build/> cmake --build . --config Release
under Unices if you have already installed doxygen
:
# builds api documents into ./docs
$build> make docs
# removes ./docs
$build> make clean_docs
these are the most important build options:
options | default | message |
---|---|---|
BUILD_MD2 | OFF | enable md2 hash (unsecure and deprecated) |
BUILD_MD4 | OFF | enable md4 hash |
BUILD_RIPEMD160 | OFF | enable ripemd160 hash |
BUILD_CFB | OFF | enable cfb (cipher feedback mode) |
BUILD_CTR | ON | enable ctr (cipher counter mode) |
BUILD_GCM | ON | enable gcm (Galois cipher mode, for aead cryptography) |
BUILD_CCM | ON | enable ccm (counter cbc-mac cipher mode, for aead cryptography) |
BUILD_DES | ON | enable des and triple-des cipher |
BUILD_BLOWFISH | OFF | enable blowfish cipher |
BUILD_CAMELLIA | OFF | enable camellia cipher |
BUILD_ARC4 | OFF | enable arc4 cipher (unsecure) |
BUILD_PK_EXPORT | ON | enable export keys in pem or der format |
BUILD_RSA_KEYGEN | ON | enable rsa key generator |
BUILD_EC | OFF | enable eckey, eckey_dh and ecdsa algorithms |
please see CMakeLists.txt for the full list.
to add or remove algorithms or features:
# cmake, ccmake or cmake-gui
$build/> cmake .. -DBUILD_CAMELLIA=ON -DBUILD_PK_EXPORT=ON -DBUILD_RSA_KEYGEN=ON
# or optionally
$build/> ccmake .
# to disable making of the test app:
$build/> cmake .. -DBUILD_TESTS=OFF
optionally if you use gcc
or clang
, you can also build mbedcrypto
as a
shared library as:
$build/> cmake .. -DBUILD_SHARED_LIBS=ON
$build/> make
buffer_t
(synonym for std::string
)
namespace mbedcrypto {
using buffer_t = std::string;
}
is widely used in mbedcrypto
api as main data container for input / output
methods.
current std::string
implementations are known to be contiguous, so I prefer
std::string
over std::vector<unsigned char>
because it helps users to
easily feed both text and binary buffers into mbedtls
api without any cast or
conversion.
see configs.hpp
mbedcrypto
objects and functions throw
mbedcrypro::exception
unless they're
tagged by noexcept
keyword.
try {
//
// mbedcrypto codes ...
//
} catch ( mbedcrypto::exception& cerr ) {
std::cerr << "the expanded error message is: "
<< cerr.what() << std::endl;
int c_error_code = cerr.code(); // the underlying error code
}
the structure of cerr.what()
:
[message, prefix or function name][(error code in hex): [module name] error string]
ex:
mbedtls_md_starts(-0x5100): MD - Bad input parameters to function
- function name: mbedtls_md_starts
- error code: -0x5100
- module name: MD (message digest)
the low level functions returns a non-zero int
as an error and are tagged by
noexcept
:
int ret = an_object.a_low_level_method(...);
if ( ret != 0 ) {
std::cout << "underlying error code: "
<< mbedcrypto::mbedtls_error_string(ret) << std::endl;
}
to list all available (included in build) algorithms:
using namespace mbedcrypto;
auto hashes = installed_hashes(); // returns std::vector<mbedcrypto::hasht_t>
std::cout << "supports " << hashes.size() << " hash algorithms: ";
for ( auto h : hashes ) { // print all installed hashes
// convert type to string
std::cout << to_string(h) << " , ";
}
// similarly
auto paddings = installed_paddings();
auto bmodes = installed_block_modes();
auto ciphers = installed_ciphers();
auto pks = installed_pks();
auto curves = installed_curves();
// convert from string to a type
auto htype = from_string<hash_t>("sha256");
if ( htype != hash_t::none ) {
}
to check for availability of a feature:
// check by type (enum class)
if ( supports(cipher_t::aes_256_cbc) && supports(pk_t::rsa) ) {
// do stuff
}
// check by algorithm name
if ( supports_hash("sha1") && supports_pk("rsa") ) {
// sign a message ...
}
// both upper and lower case are supported
if ( supports_cipher("CAMELLIA_128_CBC") ) {
}
// to check a single feature
if ( suppurts(features::aes_ni) ) {
std::cout << "this system supports AESNI (hardware accelerated AES)" << std::endl;
}
if ( supports(features::aead) && supports(cipher_bm::gcm) ) {
// do GCM authenticated encryption with additional data
}
see types.hpp
handy utility to convert binary into (or from) text:
using namespace mbedcrypto;
std::fstream fpng = open_a_png_file(...);
std::string bin_data; // binary data
fpng >> bin_data;
auto png_hex = to_hex(bin_data);
auto png_b64 = to_base64(bin_data);
REQUIRE( from_hex(png_hex) == from_base64(png_b64) );
// to get the required base64 size
size_t encode_size = base64::encode_size(bin_data);
png_b64 = base64::encode(bin_data);
REQUIRE( base64::decode_size(png_b64) == bin_data.size() );
REQUIRE_THROWS( base64::decode("invalid base64 string @#$%#^$") );
see tcodec.hpp
(cryptographic hashes also known as message digests)
using namespace mbedcrypto;
// by single shot functions
auto hash_value = make_hash(hash_t::sha256, source_data);
auto hmac_value = hmac::make(hash_t::sha1, key_data, source_data);
std::cout << to_base64(hash_value) << std::endl;
or
hash h0(hash_t::ripemd160);
hash h1(from_string<hash_t>("sha1"));
h1.start();
while ( ... ) {
h1.update(read_some_data());
}
auto hash_value = h1.finish();
// re-use
h1.start();
h1.update(...); // single or multiple updates
hash_value = h1.finish();
see hash.hpp
If a cipher block mode allows, the cipher
class automatically breaks input
data into chunks (cipher::block_size()
) and frees the user from
breaking/merging of input/output data:
using namespace mbedcrypto;
std::string source_plain_data = read_from_somewhere();
// encrypt and decrypt by single-shot functions
auto ciphered_buffer = cipher::encrypt(
cipher_t::aes_256_cbc,
padding_t::pkcs7,
initial_vector_data, // get iv size from cipher::iv_size()
key_data, // get key length in bit by cipher::key_bitlen()
source_plain_data // could be in any size because of cbc block mode
);
auto decrypted_buffer = cipher::decrypt(
cipher_t::aes_256_cbc,
padding_t::pkcs7,
initial_vector_data,
key_data,
ciphered_buffer
);
REQUIRE( source_plain_data == decrypted_buffer );
to use authenticated encryption with associated data (aka aead):
std::string the_additional_data = ...;
auto encr = cipher::encrypt_aead(
cipher_t::aes_256_ccm,
iv_data,
key_data,
the_additional_data,
source_plain_data
); ///< returns a std::tuple< computed_tag, encrypted_data >
auto decr = cipher::decrypt_aead(
cipher_t::aes_256_ccm,
iv_data,
key_data,
the_additional_data,
encr
); ///< returns a std::tuple< authentication_status, decrypted_data >
REQUIRE( std::get<0>(decr) == true ); // authenticated?
REQUIRE( std::get<1>(decr) == source_plain_data );
or by reusable object:
// construct and setup properties
cipher cipdec(cipher_t::aes_256_cbc);
cipdec
.padding(padding_t::pkcs7)
.iv(iv_data)
.key(key_data, cipher::decrypt_mode);
// by start() / update()s / finish()
cipdec.start();
std::string decrypted_data;
while ( ... ) {
decrypted_data += cipdec.update(read_some_encrypted_data());
}
decrypted_data += cipdec.finish();
REQUIRE( source_plain_data == decrypted_data );
// re-use the object
cipdec.start();
cipdec.update(...); // or multiple updates
cipdec.finish();
// single shot re-use
decrypted_data = cipdec.crypt(encrypted_data);
see cipher.hpp
to make cryptographically secure psuedo random bytes:
rnd_generator rgen;
auto random_data1 = rgen.make(256); // in bytes
// entropy and ctr_drbg are not so cheap, reuse them:
auto random_data2 = rgen.make(32); // in bytes
// update internal state with custom data (may helps entropy)
rgen.update(some_random_volatile_data);
auto nonce = rgen.make(64);
playing with rsa
keys:
using namespace mbedcrypto;
rsa pri_key;
// import from data buffer
pri_key.import_key(private_key_data, optional_password);
// or load from a file by file-name
pri_key.load_key("private_key.pem");
rsa pub_key;
pub_key.import_public_key(public_key_data);
// [optional] check matching public/private pair
REQUIRE( check_pair(pub_key, pri_key) == true );
// export keys
if ( supports(features::pk_export) ) {
auto der_data = pub_key.export_public_key(pk::der_format);
// write or share
}
// key generation
if ( supports(features::rsa_keygen) ) {
rsa pri_key;
pri_key.generate_key(2048); // a 2048bit key
// do stuff
}
auto af = pub_key.what_can_do(); // what can i do with this key?
// returns pk::action_flags (key capabilities) with following data:
// af.encrypt = true
// af.decrypt = false
// af.sign = false
// af.verify = true
// because pub_key is a valid rsa public-key
auto kinfo = pri_key.key_info();
// kinfo.N : public modulus
// kinfo.E : public exponent
// only valid if the key is a private key
// kinfo.D : private exponent
// kinfo.P : 1st prime factor
// kinfo.Q : 2nd prime factor
// kinfo.DP : D % (P - 1)
// kinfo.DQ : D % (Q - 1)
// kinfo.QP : 1 / (Q % P)
to sign and verify by rsa
:
// signature & verification
std::string message = read_message_from_somewhere();
auto signature = pri_key.sign(message, hash_t::sha256);
REQUIRE( pub_key.verify(signature, message, hash_t::sha256);
to encrypt and decrypt by rsa
:
const auto hvalue = hash::make(hash_t::sha256, message);
auto encv = pub_key.encrypt(hvalue);
auto decv = pri_key.decrypt(encv);
REQUIRE( decv == hvalue );
// or
auto encv = pub_key.encrypt(message, hash_t::sha256);
auto decv = pri_key.decrypt(encv);
REQUIRE( decv == hash::make(hash_t::sha256, message) );
see rsa.hpp
to create ec
keys from curves:
using namespace mbedcrypto;
if ( supports(features::pk_export) && supports(pk_t::eckey) ) {
ecp gen; // elliptic curve public key infrastructure
gen.generate_key(curve_t::secp224k1); // or any other supported curves
auto pri_data = gen.export_key(pk::pem_format);
auto pub_data = gen.export_public_key(pk::pem_format);
// do stuff
auto kinfo = gen.key_info(); // ecurve points and secret
std::cout
<< "\nQx (" << kinfo.Qx.bitlen() << "): " << kinfo.Qx.to_string()
<< "\nQy (" << kinfo.Qy.bitlen() << "): " << kinfo.Qy.to_string()
<< "\nQz (" << kinfo.Qz.bitlen() << "): " << kinfo.Qz.to_string()
<< "\nd (" << kinfo.D.bitlen() << "): " << kinfo.d.to_string()
<< std::endl;
}
to sign and verify by ecdsa
:
using namespace mbedcrypto;
if ( supports(pk_t::ecdsa) && supports(features::ec_keygen) ) {
ecdsa pri_key; // both pk_t::eckey and pk_t::ecdsa works
pri_key.generate_key(curve_t::secp192k1);
auto sig = pri_key.sign(message, hash_t::sha384);
ecdsa pub_key;
pub_key.import_public_key(
pri_key.export_public_key(pk::pem_format)
);
REQUIRE( pub_key.verify(sig, message, hash_t::sha384) );
}
to create a shared secret by ECDH(E)
when both ends know the curve type:
using namespace mbedcrypto;
// const auto ctype = curve_t::secp224r1 // both know the curve type
ecdh server;
auto srv_pub = server.make_peer_key(ctype);
// send srv_pub to client
ecdh client;
client.generate_key(ctype); // alternative approach to make_peer_key()
auto cli_pub = client.peer_key();
// send cli_pub to server
auto sss = server.shared_secret(cli_pub); // on server
auto css = client.shared_secret(srv_pub); // on client
REQUIRE( (sss == css) );
or if the curve parameters are defined by server at runtime as defined in RFC 4492: Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS) do:
using namespace mbedcrypto;
ecdh server;
// (only) server defines the curve type
auto skex = server.make_server_key_exchange(curve_t::secp192k1);
// send server's key exchange params to client
ecdh client;
auto cli_pub = client.make_client_peer_key(skex);
auto css = client.shared_secret();
// send cli_pub to server
auto sss = server.shared_secret(cli_pub); // on server
REQUIRE( (sss == css) );
see ecp.hpp
samples and unit tests are available under tests/tdd folder.
the test application has been built by catch:
# to list all available test tags:
$xbin/> ./tests -t
# run the tests
$xbin/> ./tests
possible output:
supports 6 hash algorithms: MD5 , SHA1 , SHA224 , SHA256 , SHA384 , SHA512 ,
supports 5 padding algorithms: PKCS7 , ONE_AND_ZEROS , ZEROS_AND_LEN , ZEROS ,
NONE ,
supports 6 block modes: NONE , ECB , CBC , CTR , GCM , CCM ,
supports 21 cipher algorithms: AES-128-ECB , AES-192-ECB , AES-256-ECB ,
AES-128-CBC , AES-192-CBC , AES-256-CBC , AES-128-CTR , AES-192-CTR ,
AES-256-CTR , AES-128-GCM , AES-192-GCM , AES-256-GCM , DES-ECB ,
DES-CBC , DES-EDE-ECB , DES-EDE-CBC , DES-EDE3-ECB , DES-EDE3-CBC ,
AES-128-CCM , AES-192-CCM , AES-256-CCM ,
this system supports AESNI (hardware accelerated AES)
this build supports AEAD (authenticated encryption with additional data)
supports 4 pk (public key) algorithms: RSA , EC , EC_DH , ECDSA ,
this build supports PK export (*.pem, *.der) facility
this build supports RSA key generation
this build supports EC (elliptic curve) key generation
supports 12 elliptic curves: SECP192R1 , SECP224R1 , SECP256R1 , SECP384R1 ,
SECP521R1 , SECP192K1 , SECP224K1 , SECP256K1 , BP256R1 , BP384R1 ,
BP512R1 , CURVE25519 ,
===============================================================================
All tests passed (952 assertions in 17 test cases)
cryptography is both complex and complicated, it requires a vast knowledge of mathematics, concepts, principles, algorithms, standards, conventions, continuous investigation of attacks, ...
As cryptography is mostly used to protect sensitive data, writing a library for it is a daunting task and difficult by any factor.
So instead of writing a library from scratch, mbedcrypto
stands on the
shoulders of giants, mbedtls
is this case.
Although mbedtls is mostly a TLS/SSL
library for embedded devices, it has already implemented the most famous and
widely used cryptographic algorithms and actively developed and maintained.
Arguably mbedtls
has cleaner code than openssl
, it's easier to read, use
and maintain, and it has been designed for efficiency and portability from
scratch (for embedded devices), and has many advantages over openssl
like as
readability, size, compiling and setup, … to name a few.
- implementing an easy-to-use, lightweight and portable
c++
library for cryptography are the main purpose ofmbedcrypto
. - there are many more algorithms in cryptographic libraries, the focus of
mbedcrypto
is on the most important or widely used algorithms, tries to be simple and not to bloat your application. - as mentioned in notes, the cryptography can be divided into several areas of study and best practices, I'm not a guru nor a specialist in this field.
If you have any ideas, critiques, suggestions or whatever you want to call it, please open an issue. I'll be happy to hear from you what you'd see in this lib. I think about all suggestions, and I try to add those that make sense.
Distributed under the MIT license. Copyright (c) 2016, Amir Zamani.