Skip to content

Commit 4781762

Browse files
sm2: add SM2PKE support (#1069)
Adds support for the SM2 public key encryption algorithm defined in China's national standard GBT.32918.4-2016 (a.k.a. SM2-4) Closes #1067 Co-authored-by: Tony Arcieri <bascule@gmail.com>
1 parent d382b1a commit 4781762

File tree

8 files changed

+689
-4
lines changed

8 files changed

+689
-4
lines changed

sm2/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ homepage = "https://github.com/RustCrypto/elliptic-curves/tree/master/sm2"
1313
repository = "https://github.com/RustCrypto/elliptic-curves"
1414
readme = "README.md"
1515
categories = ["cryptography", "no-std"]
16-
keywords = ["crypto", "ecc", "shangmi", "signature"]
16+
keywords = ["crypto", "ecc", "shangmi", "signature", "encryption"]
1717
edition = "2021"
1818
rust-version = "1.73"
1919

@@ -33,13 +33,14 @@ proptest = "1"
3333
rand_core = { version = "0.6", features = ["getrandom"] }
3434

3535
[features]
36-
default = ["arithmetic", "dsa", "pem", "std"]
36+
default = ["arithmetic", "dsa", "pke", "pem", "std"]
3737
alloc = ["elliptic-curve/alloc"]
3838
std = ["alloc", "elliptic-curve/std", "signature?/std"]
3939

4040
arithmetic = ["dep:primeorder", "elliptic-curve/arithmetic"]
4141
bits = ["arithmetic", "elliptic-curve/bits"]
4242
dsa = ["arithmetic", "dep:rfc6979", "dep:signature", "dep:sm3"]
43+
pke = ["arithmetic", "dep:sm3"]
4344
getrandom = ["rand_core/getrandom"]
4445
pem = ["elliptic-curve/pem", "pkcs8"]
4546
pkcs8 = ["elliptic-curve/pkcs8"]

sm2/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ The SM2 cryptosystem is composed of three distinct algorithms:
3333

3434
- [x] **SM2DSA**: digital signature algorithm defined in [GBT.32918.2-2016], [ISO.IEC.14888-3] (SM2-2)
3535
- [ ] **SM2KEP**: key exchange protocol defined in [GBT.32918.3-2016] (SM2-3)
36-
- [ ] **SM2PKE**: public key encryption algorithm defined in [GBT.32918.4-2016] (SM2-4)
36+
- [x] **SM2PKE**: public key encryption algorithm defined in [GBT.32918.4-2016] (SM2-4)
3737

3838
## Minimum Supported Rust Version
3939

sm2/src/arithmetic/field.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ use core::{
3434
iter::{Product, Sum},
3535
ops::{AddAssign, MulAssign, Neg, SubAssign},
3636
};
37-
use elliptic_curve::ops::Invert;
3837
use elliptic_curve::{
3938
bigint::Limb,
4039
ff::PrimeField,
40+
ops::Invert,
4141
subtle::{Choice, ConstantTimeEq, CtOption},
4242
};
4343

sm2/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ extern crate alloc;
3131
#[cfg(feature = "dsa")]
3232
pub mod dsa;
3333

34+
#[cfg(feature = "pke")]
35+
pub mod pke;
36+
3437
#[cfg(feature = "arithmetic")]
3538
mod arithmetic;
3639
#[cfg(feature = "dsa")]

sm2/src/pke.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
//! SM2 Encryption Algorithm (SM2) as defined in [draft-shen-sm2-ecdsa § 5].
2+
//!
3+
//! ## Usage
4+
//!
5+
//! NOTE: requires the `sm3` crate for digest functions and the `primeorder` crate for prime field operations.
6+
//!
7+
//! The `DecryptingKey` struct is used for decrypting messages that were encrypted using the SM2 encryption algorithm.
8+
//! It is initialized with a `SecretKey` or a non-zero scalar value and can decrypt ciphertexts using the specified decryption mode.
9+
#![cfg_attr(feature = "std", doc = "```")]
10+
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
11+
//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
12+
//! use rand_core::OsRng; // requires 'getrandom` feature
13+
//! use sm2::{
14+
//! pke::{EncryptingKey, Mode},
15+
//! {SecretKey, PublicKey}
16+
//!
17+
//! };
18+
//!
19+
//! // Encrypting
20+
//! let secret_key = SecretKey::random(&mut OsRng); // serialize with `::to_bytes()`
21+
//! let public_key = secret_key.public_key();
22+
//! let encrypting_key = EncryptingKey::new_with_mode(public_key, Mode::C1C2C3);
23+
//! let plaintext = b"plaintext";
24+
//! let ciphertext = encrypting_key.encrypt(plaintext)?;
25+
//!
26+
//! use sm2::pke::DecryptingKey;
27+
//! // Decrypting
28+
//! let decrypting_key = DecryptingKey::new_with_mode(secret_key.to_nonzero_scalar(), Mode::C1C2C3);
29+
//! assert_eq!(decrypting_key.decrypt(&ciphertext)?, plaintext);
30+
//!
31+
//! // Encrypting ASN.1 DER
32+
//! let ciphertext = encrypting_key.encrypt_der(plaintext)?;
33+
//!
34+
//! // Decrypting ASN.1 DER
35+
//! assert_eq!(decrypting_key.decrypt_der(&ciphertext)?, plaintext);
36+
//!
37+
//! Ok(())
38+
//! # }
39+
//! ```
40+
//!
41+
//!
42+
//!
43+
44+
use core::cmp::min;
45+
46+
use crate::AffinePoint;
47+
48+
#[cfg(feature = "alloc")]
49+
use alloc::vec;
50+
51+
use elliptic_curve::{
52+
bigint::{Encoding, Uint, U256},
53+
pkcs8::der::{
54+
asn1::UintRef, Decode, DecodeValue, Encode, Length, Reader, Sequence, Tag, Writer,
55+
},
56+
};
57+
58+
use elliptic_curve::{
59+
pkcs8::der::{asn1::OctetStringRef, EncodeValue},
60+
sec1::ToEncodedPoint,
61+
Result,
62+
};
63+
use sm3::digest::DynDigest;
64+
65+
#[cfg(feature = "arithmetic")]
66+
mod decrypting;
67+
#[cfg(feature = "arithmetic")]
68+
mod encrypting;
69+
70+
#[cfg(feature = "arithmetic")]
71+
pub use self::{decrypting::DecryptingKey, encrypting::EncryptingKey};
72+
73+
/// Modes for the cipher encoding/decoding.
74+
#[derive(Clone, Copy, Debug)]
75+
pub enum Mode {
76+
/// old mode
77+
C1C2C3,
78+
/// new mode
79+
C1C3C2,
80+
}
81+
/// Represents a cipher structure containing encryption-related data (asn.1 format).
82+
///
83+
/// The `Cipher` structure includes the coordinates of the elliptic curve point (`x`, `y`),
84+
/// the digest of the message, and the encrypted cipher text.
85+
pub struct Cipher<'a> {
86+
x: U256,
87+
y: U256,
88+
digest: &'a [u8],
89+
cipher: &'a [u8],
90+
}
91+
92+
impl<'a> Sequence<'a> for Cipher<'a> {}
93+
94+
impl<'a> EncodeValue for Cipher<'a> {
95+
fn value_len(&self) -> elliptic_curve::pkcs8::der::Result<Length> {
96+
UintRef::new(&self.x.to_be_bytes())?.encoded_len()?
97+
+ UintRef::new(&self.y.to_be_bytes())?.encoded_len()?
98+
+ OctetStringRef::new(self.digest)?.encoded_len()?
99+
+ OctetStringRef::new(self.cipher)?.encoded_len()?
100+
}
101+
102+
fn encode_value(&self, writer: &mut impl Writer) -> elliptic_curve::pkcs8::der::Result<()> {
103+
UintRef::new(&self.x.to_be_bytes())?.encode(writer)?;
104+
UintRef::new(&self.y.to_be_bytes())?.encode(writer)?;
105+
OctetStringRef::new(self.digest)?.encode(writer)?;
106+
OctetStringRef::new(self.cipher)?.encode(writer)?;
107+
Ok(())
108+
}
109+
}
110+
111+
impl<'a> DecodeValue<'a> for Cipher<'a> {
112+
type Error = elliptic_curve::pkcs8::der::Error;
113+
114+
fn decode_value<R: Reader<'a>>(
115+
decoder: &mut R,
116+
header: elliptic_curve::pkcs8::der::Header,
117+
) -> core::result::Result<Self, Self::Error> {
118+
decoder.read_nested(header.length, |nr| {
119+
let x = UintRef::decode(nr)?.as_bytes();
120+
let y = UintRef::decode(nr)?.as_bytes();
121+
let digest = OctetStringRef::decode(nr)?.into();
122+
let cipher = OctetStringRef::decode(nr)?.into();
123+
Ok(Cipher {
124+
x: Uint::from_be_bytes(zero_pad_byte_slice(x)?),
125+
y: Uint::from_be_bytes(zero_pad_byte_slice(y)?),
126+
digest,
127+
cipher,
128+
})
129+
})
130+
}
131+
}
132+
133+
/// Performs key derivation using a hash function and elliptic curve point.
134+
fn kdf(hasher: &mut dyn DynDigest, kpb: AffinePoint, c2: &mut [u8]) -> Result<()> {
135+
let klen = c2.len();
136+
let mut ct: i32 = 0x00000001;
137+
let mut offset = 0;
138+
let digest_size = hasher.output_size();
139+
let mut ha = vec![0u8; digest_size];
140+
let encode_point = kpb.to_encoded_point(false);
141+
142+
while offset < klen {
143+
hasher.update(encode_point.x().ok_or(elliptic_curve::Error)?);
144+
hasher.update(encode_point.y().ok_or(elliptic_curve::Error)?);
145+
hasher.update(&ct.to_be_bytes());
146+
147+
hasher
148+
.finalize_into_reset(&mut ha)
149+
.map_err(|_e| elliptic_curve::Error)?;
150+
151+
let xor_len = min(digest_size, klen - offset);
152+
xor(c2, &ha, offset, xor_len);
153+
offset += xor_len;
154+
ct += 1;
155+
}
156+
Ok(())
157+
}
158+
159+
/// XORs a portion of the buffer `c2` with a hash value.
160+
fn xor(c2: &mut [u8], ha: &[u8], offset: usize, xor_len: usize) {
161+
for i in 0..xor_len {
162+
c2[offset + i] ^= ha[i];
163+
}
164+
}
165+
166+
/// Converts a byte slice to a fixed-size array, padding with leading zeroes if necessary.
167+
pub(crate) fn zero_pad_byte_slice<const N: usize>(
168+
bytes: &[u8],
169+
) -> elliptic_curve::pkcs8::der::Result<[u8; N]> {
170+
let num_zeroes = N
171+
.checked_sub(bytes.len())
172+
.ok_or_else(|| Tag::Integer.length_error())?;
173+
174+
// Copy input into `N`-sized output buffer with leading zeroes
175+
let mut output = [0u8; N];
176+
output[num_zeroes..].copy_from_slice(bytes);
177+
Ok(output)
178+
}

0 commit comments

Comments
 (0)