-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sm2: add SM2PKE support #1069
Merged
Merged
sm2: add SM2PKE support #1069
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
//! SM2 Encryption Algorithm (SM2) as defined in [draft-shen-sm2-ecdsa § 5]. | ||
//! | ||
//! ## Usage | ||
//! | ||
//! NOTE: requires the `sm3` crate for digest functions and the `primeorder` crate for prime field operations. | ||
//! | ||
//! The `DecryptingKey` struct is used for decrypting messages that were encrypted using the SM2 encryption algorithm. | ||
//! It is initialized with a `SecretKey` or a non-zero scalar value and can decrypt ciphertexts using the specified decryption mode. | ||
#![cfg_attr(feature = "std", doc = "```")] | ||
#![cfg_attr(not(feature = "std"), doc = "```ignore")] | ||
//! # fn example() -> Result<(), Box<dyn std::error::Error>> { | ||
//! use rand_core::OsRng; // requires 'getrandom` feature | ||
//! use sm2::{ | ||
//! pke::{EncryptingKey, Mode}, | ||
//! {SecretKey, PublicKey} | ||
//! | ||
//! }; | ||
//! | ||
//! // Encrypting | ||
//! let secret_key = SecretKey::random(&mut OsRng); // serialize with `::to_bytes()` | ||
//! let public_key = secret_key.public_key(); | ||
//! let encrypting_key = EncryptingKey::new_with_mode(public_key, Mode::C1C2C3); | ||
//! let plaintext = b"plaintext"; | ||
//! let ciphertext = encrypting_key.encrypt(plaintext)?; | ||
//! | ||
//! use sm2::pke::DecryptingKey; | ||
//! // Decrypting | ||
//! let decrypting_key = DecryptingKey::new_with_mode(secret_key.to_nonzero_scalar(), Mode::C1C2C3); | ||
//! assert_eq!(decrypting_key.decrypt(&ciphertext)?, plaintext); | ||
//! | ||
//! // Encrypting ASN.1 DER | ||
//! let ciphertext = encrypting_key.encrypt_der(plaintext)?; | ||
//! | ||
//! // Decrypting ASN.1 DER | ||
//! assert_eq!(decrypting_key.decrypt_der(&ciphertext)?, plaintext); | ||
//! | ||
//! Ok(()) | ||
//! # } | ||
//! ``` | ||
//! | ||
//! | ||
//! | ||
|
||
use core::cmp::min; | ||
|
||
use crate::AffinePoint; | ||
|
||
#[cfg(feature = "alloc")] | ||
use alloc::vec; | ||
|
||
use elliptic_curve::{ | ||
bigint::{Encoding, Uint, U256}, | ||
pkcs8::der::{ | ||
asn1::UintRef, Decode, DecodeValue, Encode, Length, Reader, Sequence, Tag, Writer, | ||
}, | ||
}; | ||
|
||
use elliptic_curve::{ | ||
pkcs8::der::{asn1::OctetStringRef, EncodeValue}, | ||
sec1::ToEncodedPoint, | ||
Result, | ||
}; | ||
use sm3::digest::DynDigest; | ||
|
||
#[cfg(feature = "arithmetic")] | ||
mod decrypting; | ||
#[cfg(feature = "arithmetic")] | ||
mod encrypting; | ||
|
||
#[cfg(feature = "arithmetic")] | ||
pub use self::{decrypting::DecryptingKey, encrypting::EncryptingKey}; | ||
|
||
/// Modes for the cipher encoding/decoding. | ||
#[derive(Clone, Copy, Debug)] | ||
pub enum Mode { | ||
/// old mode | ||
C1C2C3, | ||
/// new mode | ||
C1C3C2, | ||
} | ||
/// Represents a cipher structure containing encryption-related data (asn.1 format). | ||
/// | ||
/// The `Cipher` structure includes the coordinates of the elliptic curve point (`x`, `y`), | ||
/// the digest of the message, and the encrypted cipher text. | ||
pub struct Cipher<'a> { | ||
x: U256, | ||
y: U256, | ||
digest: &'a [u8], | ||
cipher: &'a [u8], | ||
} | ||
|
||
impl<'a> Sequence<'a> for Cipher<'a> {} | ||
|
||
impl<'a> EncodeValue for Cipher<'a> { | ||
fn value_len(&self) -> elliptic_curve::pkcs8::der::Result<Length> { | ||
UintRef::new(&self.x.to_be_bytes())?.encoded_len()? | ||
+ UintRef::new(&self.y.to_be_bytes())?.encoded_len()? | ||
+ OctetStringRef::new(self.digest)?.encoded_len()? | ||
+ OctetStringRef::new(self.cipher)?.encoded_len()? | ||
} | ||
|
||
fn encode_value(&self, writer: &mut impl Writer) -> elliptic_curve::pkcs8::der::Result<()> { | ||
UintRef::new(&self.x.to_be_bytes())?.encode(writer)?; | ||
UintRef::new(&self.y.to_be_bytes())?.encode(writer)?; | ||
OctetStringRef::new(self.digest)?.encode(writer)?; | ||
OctetStringRef::new(self.cipher)?.encode(writer)?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl<'a> DecodeValue<'a> for Cipher<'a> { | ||
type Error = elliptic_curve::pkcs8::der::Error; | ||
|
||
fn decode_value<R: Reader<'a>>( | ||
decoder: &mut R, | ||
header: elliptic_curve::pkcs8::der::Header, | ||
) -> core::result::Result<Self, Self::Error> { | ||
decoder.read_nested(header.length, |nr| { | ||
let x = UintRef::decode(nr)?.as_bytes(); | ||
let y = UintRef::decode(nr)?.as_bytes(); | ||
let digest = OctetStringRef::decode(nr)?.into(); | ||
let cipher = OctetStringRef::decode(nr)?.into(); | ||
Ok(Cipher { | ||
x: Uint::from_be_bytes(zero_pad_byte_slice(x)?), | ||
y: Uint::from_be_bytes(zero_pad_byte_slice(y)?), | ||
digest, | ||
cipher, | ||
}) | ||
}) | ||
} | ||
} | ||
|
||
/// Performs key derivation using a hash function and elliptic curve point. | ||
fn kdf(hasher: &mut dyn DynDigest, kpb: AffinePoint, c2: &mut [u8]) -> Result<()> { | ||
let klen = c2.len(); | ||
let mut ct: i32 = 0x00000001; | ||
let mut offset = 0; | ||
let digest_size = hasher.output_size(); | ||
let mut ha = vec![0u8; digest_size]; | ||
let encode_point = kpb.to_encoded_point(false); | ||
|
||
while offset < klen { | ||
hasher.update(encode_point.x().ok_or(elliptic_curve::Error)?); | ||
hasher.update(encode_point.y().ok_or(elliptic_curve::Error)?); | ||
hasher.update(&ct.to_be_bytes()); | ||
|
||
hasher | ||
.finalize_into_reset(&mut ha) | ||
.map_err(|_e| elliptic_curve::Error)?; | ||
|
||
let xor_len = min(digest_size, klen - offset); | ||
xor(c2, &ha, offset, xor_len); | ||
offset += xor_len; | ||
ct += 1; | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// XORs a portion of the buffer `c2` with a hash value. | ||
fn xor(c2: &mut [u8], ha: &[u8], offset: usize, xor_len: usize) { | ||
for i in 0..xor_len { | ||
c2[offset + i] ^= ha[i]; | ||
} | ||
} | ||
|
||
/// Converts a byte slice to a fixed-size array, padding with leading zeroes if necessary. | ||
pub(crate) fn zero_pad_byte_slice<const N: usize>( | ||
bytes: &[u8], | ||
) -> elliptic_curve::pkcs8::der::Result<[u8; N]> { | ||
let num_zeroes = N | ||
.checked_sub(bytes.len()) | ||
.ok_or_else(|| Tag::Integer.length_error())?; | ||
|
||
// Copy input into `N`-sized output buffer with leading zeroes | ||
let mut output = [0u8; N]; | ||
output[num_zeroes..].copy_from_slice(bytes); | ||
Ok(output) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't work on heapless
no_std
targets, nor is thealloc
feature being enabled by thepke
feature.It seems like you could get rid of these dynamic allocations if you switched from
DynDigest
to a genericD
parameter like you have fordecrypt_digest
and bound on theOutputSizeUser
trait, which will give you access to a compile-time output size which can be used to allocate an appropriately sized array.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I once saw an issue related to using
OutputSizeUser::USIZE
to create a new array.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to solve this problem with the
hybird_array
, but I needed a little time to adjust the other places where thealloc
feature was used