Skip to content

Commit 75326d4

Browse files
committed
pkey: implement PKey#encrypt and #decrypt
Support public key encryption and decryption operations using the EVP API.
1 parent c310840 commit 75326d4

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

ext/openssl/ossl_pkey.c

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,145 @@ ossl_pkey_derive(int argc, VALUE *argv, VALUE self)
10241024
return str;
10251025
}
10261026

1027+
/*
1028+
* call-seq:
1029+
* pkey.encrypt(data [, options]) -> string
1030+
*
1031+
* Performs a public key encryption operation using +pkey+.
1032+
*
1033+
* See #decrypt for the reverse operation.
1034+
*
1035+
* Added in version 3.0. See also the man page EVP_PKEY_encrypt(3).
1036+
*
1037+
* +data+::
1038+
* A String to be encrypted.
1039+
* +options+::
1040+
* A Hash that contains algorithm specific control operations to \OpenSSL.
1041+
* See OpenSSL's man page EVP_PKEY_CTX_ctrl_str(3) for details.
1042+
*
1043+
* Example:
1044+
* pkey = OpenSSL::PKey.generate_key("RSA", rsa_keygen_bits: 2048)
1045+
* data = "secret data"
1046+
* encrypted = pkey.encrypt(data, rsa_padding_mode: "oaep")
1047+
* decrypted = pkey.decrypt(data, rsa_padding_mode: "oaep")
1048+
* p decrypted #=> "secret data"
1049+
*/
1050+
static VALUE
1051+
ossl_pkey_encrypt(int argc, VALUE *argv, VALUE self)
1052+
{
1053+
EVP_PKEY *pkey;
1054+
EVP_PKEY_CTX *ctx;
1055+
VALUE data, options, str;
1056+
size_t outlen;
1057+
int state;
1058+
1059+
GetPKey(self, pkey);
1060+
rb_scan_args(argc, argv, "11", &data, &options);
1061+
StringValue(data);
1062+
1063+
ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
1064+
if (!ctx)
1065+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
1066+
if (EVP_PKEY_encrypt_init(ctx) <= 0) {
1067+
EVP_PKEY_CTX_free(ctx);
1068+
ossl_raise(ePKeyError, "EVP_PKEY_encrypt_init");
1069+
}
1070+
if (!NIL_P(options)) {
1071+
pkey_ctx_apply_options(ctx, options, &state);
1072+
if (state) {
1073+
EVP_PKEY_CTX_free(ctx);
1074+
rb_jump_tag(state);
1075+
}
1076+
}
1077+
if (EVP_PKEY_encrypt(ctx, NULL, &outlen,
1078+
(unsigned char *)RSTRING_PTR(data),
1079+
RSTRING_LEN(data)) <= 0) {
1080+
EVP_PKEY_CTX_free(ctx);
1081+
ossl_raise(ePKeyError, "EVP_PKEY_encrypt");
1082+
}
1083+
if (outlen > LONG_MAX) {
1084+
EVP_PKEY_CTX_free(ctx);
1085+
rb_raise(ePKeyError, "encrypted data would be too large");
1086+
}
1087+
str = ossl_str_new(NULL, (long)outlen, &state);
1088+
if (state) {
1089+
EVP_PKEY_CTX_free(ctx);
1090+
rb_jump_tag(state);
1091+
}
1092+
if (EVP_PKEY_encrypt(ctx, (unsigned char *)RSTRING_PTR(str), &outlen,
1093+
(unsigned char *)RSTRING_PTR(data),
1094+
RSTRING_LEN(data)) <= 0) {
1095+
EVP_PKEY_CTX_free(ctx);
1096+
ossl_raise(ePKeyError, "EVP_PKEY_encrypt");
1097+
}
1098+
EVP_PKEY_CTX_free(ctx);
1099+
rb_str_set_len(str, outlen);
1100+
return str;
1101+
}
1102+
1103+
/*
1104+
* call-seq:
1105+
* pkey.decrypt(data [, options]) -> string
1106+
*
1107+
* Performs a public key decryption operation using +pkey+.
1108+
*
1109+
* See #encrypt for a description of the parameters and an example.
1110+
*
1111+
* Added in version 3.0. See also the man page EVP_PKEY_decrypt(3).
1112+
*/
1113+
static VALUE
1114+
ossl_pkey_decrypt(int argc, VALUE *argv, VALUE self)
1115+
{
1116+
EVP_PKEY *pkey;
1117+
EVP_PKEY_CTX *ctx;
1118+
VALUE data, options, str;
1119+
size_t outlen;
1120+
int state;
1121+
1122+
GetPKey(self, pkey);
1123+
rb_scan_args(argc, argv, "11", &data, &options);
1124+
StringValue(data);
1125+
1126+
ctx = EVP_PKEY_CTX_new(pkey, /* engine */NULL);
1127+
if (!ctx)
1128+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
1129+
if (EVP_PKEY_decrypt_init(ctx) <= 0) {
1130+
EVP_PKEY_CTX_free(ctx);
1131+
ossl_raise(ePKeyError, "EVP_PKEY_decrypt_init");
1132+
}
1133+
if (!NIL_P(options)) {
1134+
pkey_ctx_apply_options(ctx, options, &state);
1135+
if (state) {
1136+
EVP_PKEY_CTX_free(ctx);
1137+
rb_jump_tag(state);
1138+
}
1139+
}
1140+
if (EVP_PKEY_decrypt(ctx, NULL, &outlen,
1141+
(unsigned char *)RSTRING_PTR(data),
1142+
RSTRING_LEN(data)) <= 0) {
1143+
EVP_PKEY_CTX_free(ctx);
1144+
ossl_raise(ePKeyError, "EVP_PKEY_decrypt");
1145+
}
1146+
if (outlen > LONG_MAX) {
1147+
EVP_PKEY_CTX_free(ctx);
1148+
rb_raise(ePKeyError, "decrypted data would be too large");
1149+
}
1150+
str = ossl_str_new(NULL, (long)outlen, &state);
1151+
if (state) {
1152+
EVP_PKEY_CTX_free(ctx);
1153+
rb_jump_tag(state);
1154+
}
1155+
if (EVP_PKEY_decrypt(ctx, (unsigned char *)RSTRING_PTR(str), &outlen,
1156+
(unsigned char *)RSTRING_PTR(data),
1157+
RSTRING_LEN(data)) <= 0) {
1158+
EVP_PKEY_CTX_free(ctx);
1159+
ossl_raise(ePKeyError, "EVP_PKEY_decrypt");
1160+
}
1161+
EVP_PKEY_CTX_free(ctx);
1162+
rb_str_set_len(str, outlen);
1163+
return str;
1164+
}
1165+
10271166
/*
10281167
* INIT
10291168
*/
@@ -1124,6 +1263,8 @@ Init_ossl_pkey(void)
11241263
rb_define_method(cPKey, "sign", ossl_pkey_sign, -1);
11251264
rb_define_method(cPKey, "verify", ossl_pkey_verify, -1);
11261265
rb_define_method(cPKey, "derive", ossl_pkey_derive, -1);
1266+
rb_define_method(cPKey, "encrypt", ossl_pkey_encrypt, -1);
1267+
rb_define_method(cPKey, "decrypt", ossl_pkey_decrypt, -1);
11271268

11281269
id_private_q = rb_intern("private?");
11291270

test/openssl/test_pkey_rsa.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,40 @@ def test_sign_verify_pss
173173
}
174174
end
175175

176+
def test_encrypt_decrypt
177+
rsapriv = Fixtures.pkey("rsa-1")
178+
rsapub = dup_public(rsapriv)
179+
180+
# Defaults to PKCS #1 v1.5
181+
raw = "data"
182+
enc = rsapub.encrypt(raw)
183+
assert_equal raw, rsapriv.decrypt(enc)
184+
185+
# Invalid options
186+
assert_raise(OpenSSL::PKey::PKeyError) {
187+
rsapub.encrypt(raw, { "nonexistent" => "option" })
188+
}
189+
end
190+
191+
def test_encrypt_decrypt_legacy
192+
rsapriv = Fixtures.pkey("rsa-1")
193+
rsapub = dup_public(rsapriv)
194+
195+
# Defaults to PKCS #1 v1.5
196+
raw = "data"
197+
enc_legacy = rsapub.public_encrypt(raw)
198+
assert_equal raw, rsapriv.decrypt(enc_legacy)
199+
enc_new = rsapub.encrypt(raw)
200+
assert_equal raw, rsapriv.private_decrypt(enc_new)
201+
202+
# OAEP with default parameters
203+
raw = "data"
204+
enc_legacy = rsapub.public_encrypt(raw, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
205+
assert_equal raw, rsapriv.decrypt(enc_legacy, { "rsa_padding_mode" => "oaep" })
206+
enc_new = rsapub.encrypt(raw, { "rsa_padding_mode" => "oaep" })
207+
assert_equal raw, rsapriv.private_decrypt(enc_legacy, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
208+
end
209+
176210
def test_export
177211
rsa1024 = Fixtures.pkey("rsa1024")
178212
key = OpenSSL::PKey::RSA.new

0 commit comments

Comments
 (0)