encryption/asymmetric/
rsa.rs1use rand_core::OsRng;
23use rsa::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
24use rsa::traits::PublicKeyParts;
25use rsa::{Oaep, RsaPrivateKey, RsaPublicKey};
26use sha2::Sha256;
27
28use crate::contract::{AsymmetricCipher, Identified};
29use crate::error::{Error, Result};
30use crate::internal::{hex_decode, hex_encode};
31
32use super::KeyPairHex;
33
34const LABEL: &str = "RSA-OAEP-SHA256";
35
36const MIN_KEY_BITS: usize = 2048;
38
39const SHA256_OUTPUT_LEN: usize = 32;
41
42#[derive(Debug, Default, Clone, Copy)]
44pub struct RsaOaepSha256Service;
45
46impl RsaOaepSha256Service {
47 pub fn encrypt(plaintext: &[u8], public_key_hex: &str) -> Result<String> {
49 let public_key = parse_public_key(public_key_hex)?;
50
51 let max_plaintext = public_key
56 .size()
57 .checked_sub(2 * SHA256_OUTPUT_LEN + 2)
58 .ok_or(Error::EncryptionFailed { label: LABEL })?;
59 if plaintext.len() > max_plaintext {
60 return Err(Error::EncryptionFailed { label: LABEL });
61 }
62
63 let ciphertext = public_key
64 .encrypt(&mut OsRng, Oaep::new::<Sha256>(), plaintext)
65 .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
66 Ok(hex_encode(&ciphertext))
67 }
68
69 pub fn decrypt(ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
71 let private_key = parse_private_key(private_key_hex)?;
72 let ciphertext =
73 hex_decode(ciphertext_hex).map_err(|_| Error::DecryptionFailed { label: LABEL })?;
74
75 private_key
77 .decrypt_blinded(&mut OsRng, Oaep::new::<Sha256>(), &ciphertext)
78 .map_err(|_| Error::DecryptionFailed { label: LABEL })
79 }
80
81 pub fn generate_key_pair_hex(bits: usize) -> Result<KeyPairHex> {
86 if bits < MIN_KEY_BITS {
87 return Err(Error::InvalidKeyLength {
90 label: LABEL,
91 expected: MIN_KEY_BITS,
92 got: bits,
93 });
94 }
95
96 let private_key = RsaPrivateKey::new(&mut OsRng, bits)
97 .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
98 let private_key_der = private_key
99 .to_pkcs8_der()
100 .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
101 let public_key_der = private_key
102 .to_public_key()
103 .to_public_key_der()
104 .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
105
106 Ok(KeyPairHex {
107 private_key_hex: hex_encode(private_key_der.as_bytes()),
108 public_key_hex: hex_encode(public_key_der.as_bytes()),
109 })
110 }
111}
112
113#[derive(Debug, Default, Clone, Copy)]
116pub struct RsaOaepSha256Cipher;
117
118impl RsaOaepSha256Cipher {
119 pub const IDENTIFIER: &'static str = "rsa-oaep-sha256";
120
121 pub fn new() -> Self {
122 Self
123 }
124}
125
126impl Identified for RsaOaepSha256Cipher {
127 fn identifier(&self) -> &str {
128 Self::IDENTIFIER
129 }
130}
131
132impl AsymmetricCipher for RsaOaepSha256Cipher {
133 fn encrypt(&self, plaintext: &[u8], public_key_hex: &str) -> Result<String> {
134 RsaOaepSha256Service::encrypt(plaintext, public_key_hex)
135 }
136
137 fn decrypt(&self, ciphertext_hex: &str, private_key_hex: &str) -> Result<Vec<u8>> {
138 RsaOaepSha256Service::decrypt(ciphertext_hex, private_key_hex)
139 }
140}
141
142fn parse_public_key(public_key_hex: &str) -> Result<RsaPublicKey> {
144 let der = hex_decode(public_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
145 RsaPublicKey::from_public_key_der(&der).map_err(|_| Error::InvalidKey { label: LABEL })
146}
147
148fn parse_private_key(private_key_hex: &str) -> Result<RsaPrivateKey> {
150 let der = hex_decode(private_key_hex).map_err(|_| Error::InvalidKey { label: LABEL })?;
151 RsaPrivateKey::from_pkcs8_der(&der).map_err(|_| Error::InvalidKey { label: LABEL })
152}
153
154#[cfg(test)]
155mod tests {
156 use std::sync::OnceLock;
157
158 use super::*;
159
160 const WYCHEPROOF_PKCS8_HEX: &str = "308204bd020100300d06092a864886f70d0101010500048204a7308204a3020100028201\
167 0100a2b451a07d0aa5f96e455671513550514a8a5b462ebef717094fa1fee82224e637f9\
168 746d3f7cafd31878d80325b6ef5a1700f65903b469429e89d6eac8845097b5ab393189db\
169 92512ed8a7711a1253facd20f79c15e8247f3d3e42e46e48c98e254a2fe9765313a03eff\
170 8f17e1a029397a1fa26a8dce26f490ed81299615d9814c22da610428e09c7d9658594266\
171 f5c021d0fceca08d945a12be82de4d1ece6b4c03145b5d3495d4ed5411eb878daf05fd7a\
172 fc3e09ada0f1126422f590975a1969816f48698bcbba1b4d9cae79d460d8f9f85e797500\
173 5d9bc22c4e5ac0f7c1a45d12569a62807d3b9a02e5a530e773066f453d1f5b4c2e9cf782\
174 0283f742b9d502030100010282010024cdc62317f5d72a6f6ba6cc9632899b01d1ff2886\
175 7d72f61688995bc855a4e420a8405250089bdb13cf8e09543827b748b9d27fbb2b4d9e20\
176 af8c5a6a862796d1a4cc18ad16ea678bc1bd4a83bbbe9c5e57453b5ce7388e41a3ba4ce2\
177 b77b4438a229e954f720dae0353dc088ac8a76b26dc276f8e1b7851ddd6398ad16ff2e78\
178 195123b9b036e945c38c9d12434f6df76fe22359eb3e1ac9c011678fc926fad3ae475a4f\
179 ffff55feb2d147e9c894f4c0e29a599e762462482d968bf42780945fc0d2c31c573c4431\
180 b8f4fe8b8c67bec815abd44f7a86edca1c2308737358d2c2ae5e2e0e2dadf73098026237\
181 7e58b13b7d9992060a0bc870ccfdb4a9319ee102818100dc431050f782e894fb5248247d\
182 98cb7d58b8d1e24f3b55d041c56e4de086b0d5bb028bda42eeb5d234d5681e5809d415e6\
183 a289ad4cfbf78f978f6c35814f50eebff1c5b80a69f788e81e6bab5ddaa78369d659d143\
184 ec6f17e79813a575cfad9c569156b90113e2e9110ad9e7b48a1c9348a6e653321191290e\
185 a36cfb3a5b18f102818100bd1a81e7977f9898122273ae3222b598ea5fb19eb4eabc3830\
186 8a5e32196603b2e500ffb79f5b886816611debc472fac45544070beb057c941378a6868a\
187 f3b7a03d3f9880ec47d5e089b94fbde542aba9ae8d72c57088d7abf5b131f39098f7bc16\
188 0f90536abc9492fd4e06f3ed7299d4b97bb03677207d95669f140cfbc20f2502818100a9\
189 4b528b28f291599121d91952ffd1c7f21d7c1479d99d478885fb161870ee1218bf084726\
190 12dbe5497e8d9c650688e09c786961ae3e2c354dc48ae34514759c4c23c4588488961dc0\
191 6b414e61c0e1e7fbbd2923d31532fe289f96da220711e58c14019808e00414276933bb07\
192 e4efb9b4a9b37656917205209f33f09515d7c10281803af0e72a933aef09ff2503df78ba\
193 fed531c02ff1a2bc437c540cdcbd4ad35435cf511763596543480629b114ca7f780ff7ef\
194 a32ea0cb6e000d6d9ea1f2ef71fd9cf9948422a165557e37e755edfe70d90b920502eb47\
195 8bc98a63f788ce3a0f856d6ede7251a383bfa8fa480a81a925af7b3cc538c4bab8c9f759\
196 7ffb68011d8d0281802640fbfbcfefb163ee7a87b6483a66ee41f956d90fa8a7939bfc04\
197 2ee0924b1b7993d0445f758d51933e85179c0320b0c968b48a91c38b5be923e1097c0c56\
198 2f88d42294b6a2759bafa5428a74f1270874e45f6fcc60f21602de5eccd143cf31241f59\
199 21b5ad3983fb54ef17be3b285367e50c999c67247b552fe4bfce945f7b";
200 const WYCHEPROOF_CT_VALID: &str = "5eab3f0741e63986ed647d53e1cd71df041986900803d0f99c68355d249a15a47dc5b4f7\
202 0a191477654299e5a2731f3b4eec76dea18262fc696ac794e5f66cbfcddac4472c578e24\
203 6c26707598055584540b839836b1404c5611ae558a984cee8fd036cea924e0be2474a940\
204 f61e0acc14fcae95ebdc59942a9ce9af9a9c81999f7f6815f057ffdc2533cb15d6391d1e\
205 2d95f16f9c04209c889a4c359c7d2926d28a66e2b030a416b928d2825627998e5191fb49\
206 83a6e65024262d94fc09187a2d78162122433251d1bfcc8e507d06eba2d229c10031261d\
207 a32ab8ccd15f1c5f9fbf07ed158483d736a110af4b44d6a4da60d6cb519b4454213cf9f0\
208 dc560f2b";
209 const WYCHEPROOF_CT_N_MINUS_1: &str = "a2b451a07d0aa5f96e455671513550514a8a5b462ebef717094fa1fee82224e637f9746d\
211 3f7cafd31878d80325b6ef5a1700f65903b469429e89d6eac8845097b5ab393189db9251\
212 2ed8a7711a1253facd20f79c15e8247f3d3e42e46e48c98e254a2fe9765313a03eff8f17\
213 e1a029397a1fa26a8dce26f490ed81299615d9814c22da610428e09c7d9658594266f5c0\
214 21d0fceca08d945a12be82de4d1ece6b4c03145b5d3495d4ed5411eb878daf05fd7afc3e\
215 09ada0f1126422f590975a1969816f48698bcbba1b4d9cae79d460d8f9f85e7975005d9b\
216 c22c4e5ac0f7c1a45d12569a62807d3b9a02e5a530e773066f453d1f5b4c2e9cf7820283\
217 f742b9d4";
218 const WYCHEPROOF_MSG_VALID_HEX: &str = "54657374"; fn wycheproof_keypair_hex() -> KeyPairHex {
223 let der = hex_decode(WYCHEPROOF_PKCS8_HEX).unwrap();
224 let private_key = RsaPrivateKey::from_pkcs8_der(&der).unwrap();
225 let public_der = private_key.to_public_key().to_public_key_der().unwrap();
226 KeyPairHex {
227 private_key_hex: WYCHEPROOF_PKCS8_HEX.to_string(),
228 public_key_hex: hex_encode(public_der.as_bytes()),
229 }
230 }
231
232 fn generated_keypair() -> &'static KeyPairHex {
234 static PAIR: OnceLock<KeyPairHex> = OnceLock::new();
235 PAIR.get_or_init(|| RsaOaepSha256Service::generate_key_pair_hex(MIN_KEY_BITS).unwrap())
236 }
237
238 #[test]
239 fn wycheproof_fixed_vectors() {
240 let pair = wycheproof_keypair_hex();
241
242 assert_eq!(
244 RsaOaepSha256Service::decrypt(WYCHEPROOF_CT_VALID, &pair.private_key_hex).unwrap(),
245 hex_decode(WYCHEPROOF_MSG_VALID_HEX).unwrap()
246 );
247
248 let ciphertext = RsaOaepSha256Service::encrypt(b"Test", &pair.public_key_hex).unwrap();
250 assert_eq!(
251 RsaOaepSha256Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap(),
252 b"Test"
253 );
254
255 for ciphertext in [WYCHEPROOF_CT_N_MINUS_1, ""] {
257 assert_eq!(
258 RsaOaepSha256Service::decrypt(ciphertext, &pair.private_key_hex).unwrap_err(),
259 Error::DecryptionFailed { label: LABEL }
260 );
261 }
262 }
263
264 #[test]
265 fn keypair_roundtrip_self_parse_and_shapes() {
266 let pair = generated_keypair();
267
268 let ciphertext =
271 RsaOaepSha256Service::encrypt("RSA 数据 🎉".as_bytes(), &pair.public_key_hex).unwrap();
272 assert_eq!(ciphertext.len(), 512);
273 assert_eq!(
274 RsaOaepSha256Service::decrypt(&ciphertext, &pair.private_key_hex).unwrap(),
275 "RSA 数据 🎉".as_bytes()
276 );
277
278 for der_hex in [&pair.private_key_hex, &pair.public_key_hex] {
280 assert!(der_hex.starts_with("30"));
281 assert!(der_hex.len().is_multiple_of(2));
282 }
283 }
284
285 #[test]
286 fn wrong_private_key_or_tampered_ciphertext_fails() {
287 let wycheproof = wycheproof_keypair_hex();
288 let generated = generated_keypair();
289
290 let ciphertext =
292 RsaOaepSha256Service::encrypt(b"secret", &wycheproof.public_key_hex).unwrap();
293 assert_eq!(
294 RsaOaepSha256Service::decrypt(&ciphertext, &generated.private_key_hex).unwrap_err(),
295 Error::DecryptionFailed { label: LABEL }
296 );
297
298 let mut tampered = ciphertext.clone();
300 let last = tampered.len() - 1;
301 let flipped = if tampered.as_bytes()[last] == b'0' {
302 '1'
303 } else {
304 '0'
305 };
306 tampered.replace_range(last.., &flipped.to_string());
307 assert_eq!(
308 RsaOaepSha256Service::decrypt(&tampered, &wycheproof.private_key_hex).unwrap_err(),
309 Error::DecryptionFailed { label: LABEL }
310 );
311 }
312
313 #[test]
314 fn oversized_plaintext_is_rejected() {
315 let pair = wycheproof_keypair_hex();
316 let max_plaintext = [0u8; 190];
318 RsaOaepSha256Service::encrypt(&max_plaintext, &pair.public_key_hex).unwrap();
319
320 let too_long = [0u8; 191];
321 assert_eq!(
322 RsaOaepSha256Service::encrypt(&too_long, &pair.public_key_hex).unwrap_err(),
323 Error::EncryptionFailed { label: LABEL }
324 );
325 }
326
327 #[test]
328 fn malformed_keys_are_rejected() {
329 let pair = wycheproof_keypair_hex();
330
331 assert_eq!(
333 RsaOaepSha256Service::encrypt(b"x", "zz").unwrap_err(),
334 Error::InvalidKey { label: LABEL }
335 );
336 assert_eq!(
337 RsaOaepSha256Service::decrypt("00", "zz").unwrap_err(),
338 Error::InvalidKey { label: LABEL }
339 );
340 assert_eq!(
342 RsaOaepSha256Service::encrypt(b"x", "deadbeef").unwrap_err(),
343 Error::InvalidKey { label: LABEL }
344 );
345 let truncated = &pair.private_key_hex[..pair.private_key_hex.len() - 2];
346 assert_eq!(
347 RsaOaepSha256Service::decrypt("00", truncated).unwrap_err(),
348 Error::InvalidKey { label: LABEL }
349 );
350 assert_eq!(
352 RsaOaepSha256Service::decrypt("00", &pair.public_key_hex).unwrap_err(),
353 Error::InvalidKey { label: LABEL }
354 );
355 }
356
357 #[test]
358 fn key_bits_below_floor_are_rejected() {
359 for bits in [512, 1024, 2047] {
360 assert_eq!(
361 RsaOaepSha256Service::generate_key_pair_hex(bits).unwrap_err(),
362 Error::InvalidKeyLength {
363 label: LABEL,
364 expected: MIN_KEY_BITS,
365 got: bits,
366 }
367 );
368 }
369 }
370
371 #[test]
372 fn cipher_trait_matches_service() {
373 let cipher = RsaOaepSha256Cipher::new();
374 assert_eq!(cipher.identifier(), "rsa-oaep-sha256");
375
376 let pair = wycheproof_keypair_hex();
377 let ciphertext = cipher.encrypt(b"via trait", &pair.public_key_hex).unwrap();
378 assert_eq!(
379 cipher.decrypt(&ciphertext, &pair.private_key_hex).unwrap(),
380 b"via trait"
381 );
382 }
383}