Skip to content

Commit 334341d

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

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

ext/openssl/ossl_pkey.c

+141
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,145 @@ ossl_pkey_derive(int argc, VALUE *argv, VALUE self)
10221022
return str;
10231023
}
10241024

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

11251266
id_private_q = rb_intern("private?");
11261267

test/openssl/test_pkey_rsa.rb

+34
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,40 @@ def test_sign_verify_pss
174174
}
175175
end
176176

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

0 commit comments

Comments
 (0)