diff --git a/tdutils/td/utils/crypto.cpp b/tdutils/td/utils/crypto.cpp index 3ef0193eb..afacf1532 100644 --- a/tdutils/td/utils/crypto.cpp +++ b/tdutils/td/utils/crypto.cpp @@ -15,11 +15,14 @@ #if TD_HAVE_OPENSSL #include +#include #include #include #include #include #include +#include +#include #endif #if TD_HAVE_ZLIB @@ -554,4 +557,67 @@ uint64 crc64(Slice data) { return crc64_partial(data, static_cast(-1)) ^ static_cast(-1); } +Result rsa_encrypt_pkcs1_oaep(Slice public_key, Slice data) { + BIO *mem_bio = BIO_new_mem_buf(public_key.data(), narrow_cast(public_key.size())); + SCOPE_EXIT { + BIO_vfree(mem_bio); + }; + + EVP_PKEY *pkey = PEM_read_bio_PUBKEY(mem_bio, nullptr, nullptr, nullptr); + if (!pkey) { + return Status::Error("Cannot read public key"); + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (!ctx) { + return Status::Error("Cannot create EVP_PKEY_CTX"); + } + if (EVP_PKEY_encrypt_init(ctx) <= 0) { + return Status::Error("Cannot init EVP_PKEY_CTX"); + } + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) { + return Status::Error("Cannot set RSA_PKCS1_OAEP padding in EVP_PKEY_CTX"); + } + size_t outlen; + if (EVP_PKEY_encrypt(ctx, nullptr, &outlen, data.ubegin(), data.size()) <= 0) { + return Status::Error("Cannot calculate encrypted length"); + } + BufferSlice res(outlen); + if (EVP_PKEY_encrypt(ctx, res.as_slice().ubegin(), &outlen, data.ubegin(), data.size()) <= 0) { + ERR_print_errors_fp(stderr); + return Status::Error("Cannot encrypt"); + } + return std::move(res); +} + +Result rsa_decrypt_pkcs1_oaep(Slice private_key, Slice data) { + BIO *mem_bio = BIO_new_mem_buf(private_key.data(), narrow_cast(private_key.size())); + SCOPE_EXIT { + BIO_vfree(mem_bio); + }; + + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(mem_bio, nullptr, nullptr, nullptr); + if (!pkey) { + return Status::Error("Cannot read private key"); + } + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (!ctx) { + return Status::Error("Cannot create EVP_PKEY_CTX"); + } + if (EVP_PKEY_decrypt_init(ctx) <= 0) { + return Status::Error("Cannot init EVP_PKEY_CTX"); + } + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) { + return Status::Error("Cannot set RSA_PKCS1_OAEP padding in EVP_PKEY_CTX"); + } + size_t outlen; + if (EVP_PKEY_decrypt(ctx, nullptr, &outlen, data.ubegin(), data.size()) <= 0) { + return Status::Error("Cannot calculate decrypted length"); + } + BufferSlice res(outlen); + if (EVP_PKEY_decrypt(ctx, res.as_slice().ubegin(), &outlen, data.ubegin(), data.size()) <= 0) { + return Status::Error("Cannot decrypt"); + } + return std::move(res); +} + } // namespace td diff --git a/tdutils/td/utils/crypto.h b/tdutils/td/utils/crypto.h index 6cb6d1adf..6e32e08a1 100644 --- a/tdutils/td/utils/crypto.h +++ b/tdutils/td/utils/crypto.h @@ -8,6 +8,8 @@ #include "td/utils/common.h" #include "td/utils/Slice.h" +#include "td/utils/buffer.h" +#include "td/utils/Status.h" namespace td { @@ -81,6 +83,10 @@ void md5(Slice input, MutableSlice output); void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice dest); void hmac_sha256(Slice key, Slice message, MutableSlice dest); +// Interface may be improved +Result rsa_encrypt_pkcs1_oaep(Slice public_key, Slice data); +Result rsa_decrypt_pkcs1_oaep(Slice public_key, Slice data); + void init_openssl_threads(); #endif diff --git a/tdutils/test/crypto.cpp b/tdutils/test/crypto.cpp index faf4ef61a..5d7128929 100644 --- a/tdutils/test/crypto.cpp +++ b/tdutils/test/crypto.cpp @@ -164,3 +164,53 @@ TEST(Crypto, crc64) { ASSERT_EQ(answers[i], td::crc64(strings[i])); } } + +static td::Slice rsa_private_key = R"ABCD( +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDeYT5/prmLEa2Q +tZND+UwTmif8kl2VlXaMCjj1k1lJJq8BqS8cVM2vPnOPzFoiC2LYykhm4kk7goCC +ZH6wez9yakg28fcq0Ycv0x8DL1K+VKHJuwIhVfQs//IY1/cBOrMESc+NQowPbv1t +TIFxBO2gebnpLuseht8ix7XtpGC4qAaHN2aEvT2cRsnA76TAK1RVxf1OYGUFBDzY +318WpVZfVIjcQ7K9+eU6b2Yb84VLlvJXw3e1rvw+fBzx2EjpD4zhXy11YppWDyV6 +HEb2hs3cGS/LbHfHvdcSfil2omaJP97MDEEY2HFxjR/E5CEf2suvPzX4XS3RE+S3 +2aEJaaQbAgMBAAECggEAKo3XRNwls0wNt5xXcvF4smOUdUuY5u/0AHZQUgYBVvM1 +GA9E+ZnsxjUgLgs/0DX3k16aHj39H4sohksuxxy+lmlqKkGBN8tioC85RwW+Qre1 +QgIsNS7ai+XqcQCavrx51z88nV53qNhnXIwAVR1JT6Ubg1i8G1pZxrEKyk/jRlJd +mGjf6vjitH//PPkghPJ/D42k93YRcy+duOgqYDQpLZp8DiEGfYrX10B1H7HrWLV+ +Wp5KO1YXtKgQUplj6kYy72bVajbxYTvzgjaaKsh74jBO0uT3tHTtXG0dcKGb0VR/ +cqP/1H/lC9bAnAqAGefNusGJQZIElvTsrpIQXOeZsQKBgQD2W04S+FjqYYFjnEFX +6eL4it01afs5M3/C6CcI5JQtN6p+Na4NCSILol33xwhakn87zqdADHawBYQVQ8Uw +dPurl805wfkzN3AbfdDmtx0IJ8vK4HFpktRjfpwBVhlVtm1doAYFqqsuCF2vWW1t +mM2YOSq4AnRHCeBb/P6kRIW0MwKBgQDnFawKKqiC4tuyBOkkEhexlm7x9he0md7D +3Z2hc3Bmdcq1niw4wBq3HUxGLReGCcSr5epKSQwkunlTn5ZSC6Rmbe4zxsGIwbb3 +5W3342swBaoxEIuBokBvZ/xUOXVwiqKj+S/NzVkZcnT6K9V/HnUCQR+JBbQxFQaX +iiezcjKoeQKBgCIVUcDoIQ0UPl10ocmy7xbpx177calhSZzCl5vwW9vBptHdRV5C +VDZ92ThNjgdR205/8b23u7fwm2yBusdQd/0ufFMwVfTTB6yWBI/W56pYLya7VJWB +nebB/n1k1w53tbvNRugDy7kLqUJ4Qd521ILp7dIVbNbjM+omH2jEnibnAoGBAIM5 +a1jaoJay/M86uqohHBNcuePtO8jzF+1iDAGC7HFCsrov+CzB6mnR2V6AfLtBEM4M +4d8NXDf/LKawGUy+D72a74m3dG+UkbJ0Nt5t5pB+pwb1vkL/QFgDVOb/OhGOqI01 +FFBqLA6nUIZAHhzxzsBY+u90rb6xkey8J49faiUBAoGAaMgOgEvQB5H19ZL5tMkl +A/DKtTz/NFzN4Zw/vNPVb7eNn4jg9M25d9xqvL4acOa+nuV3nLHbcUWE1/7STXw1 +gT58CvoEmD1AiP95nup+HKHENJ1DWMgF5MDfVQwGCvWP5/Qy89ybr0eG8HjbldbN +MpSmzz2wOz152oGdOd3syT4= +-----END PRIVATE KEY----- +)ABCD"; + +static td::Slice rsa_public_key = R"ABCD( +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3mE+f6a5ixGtkLWTQ/lM +E5on/JJdlZV2jAo49ZNZSSavAakvHFTNrz5zj8xaIgti2MpIZuJJO4KAgmR+sHs/ +cmpINvH3KtGHL9MfAy9SvlShybsCIVX0LP/yGNf3ATqzBEnPjUKMD279bUyBcQTt +oHm56S7rHobfIse17aRguKgGhzdmhL09nEbJwO+kwCtUVcX9TmBlBQQ82N9fFqVW +X1SI3EOyvfnlOm9mG/OFS5byV8N3ta78Pnwc8dhI6Q+M4V8tdWKaVg8lehxG9obN +3Bkvy2x3x73XEn4pdqJmiT/ezAxBGNhxcY0fxOQhH9rLrz81+F0t0RPkt9mhCWmk +GwIDAQAB +-----END PUBLIC KEY----- +)ABCD"; + +TEST(Crypto, rsa) { + auto value = td::rand_string('a', 'z', 200); + auto encrypted_value = td::rsa_encrypt_pkcs1_oaep(rsa_public_key, value).move_as_ok(); + auto decrypted_value = td::rsa_decrypt_pkcs1_oaep(rsa_private_key, encrypted_value.as_slice()).move_as_ok(); + ASSERT_TRUE(decrypted_value.as_slice().truncate(value.size()) == value); +}