ring API Compatibility (ring-native-ossl)
ring-native-ossl provides a structural drop-in for the
ring cryptography crate, backed entirely by
OpenSSL via native-ossl. Code that reaches ring through a selectable backend
or feature flag can substitute this crate without changing call sites.
Adding the dependency
[dependencies]
ring-native-ossl = { path = "../ring-native-ossl" }
To use it as a transparent alias at the module level:
use ring_native_ossl as ring;
Module overview
| Module | ring equivalent | Provided by |
|---|---|---|
digest | ring::digest | DigestAlg + DigestCtx |
hmac | ring::hmac | HmacCtx / MacCtx |
hkdf | ring::hkdf | HkdfBuilder |
aead | ring::aead | CipherAlg + AeadEncryptCtx/AeadDecryptCtx |
agreement | ring::agreement | Pkey<Private> + DeriveCtx |
signature | ring::signature | Pkey<Private> + Signer/Verifier |
rand | ring::rand | Rand::fill |
error | ring::error | — |
digest — Hashing
use ring_native_ossl::digest::{self, Context, SHA256, SHA384, SHA512};
// One-shot
let d = digest::digest(&SHA256, b"hello world");
println!("{}", hex::encode(d.as_ref()));
// Streaming
let mut ctx = Context::new(&SHA256);
ctx.update(b"hello ");
ctx.update(b"world");
let d = ctx.finish();
Algorithms
| Static | Digest |
|---|---|
SHA256 | SHA-256 |
SHA384 | SHA-384 |
SHA512 | SHA-512 |
SHA1_FOR_LEGACY_USE_ONLY | SHA-1 (avoid in new code) |
Context
impl Context {
pub fn new(algorithm: &'static Algorithm) -> Self;
pub fn update(&mut self, data: &[u8]);
pub fn clone(&self) -> Self; // fork mid-stream
pub fn finish(self) -> Digest;
}
impl Digest {
pub fn as_ref(&self) -> &[u8];
pub fn algorithm(&self) -> &'static Algorithm;
}
hmac — Message Authentication
use ring_native_ossl::hmac::{self, Key, HMAC_SHA256};
let key = Key::new(HMAC_SHA256, b"my-secret-key");
// Sign
let tag = hmac::sign(&key, b"message");
// Verify
hmac::verify(&key, b"message", tag.as_ref()).unwrap();
Algorithms
| Static | MAC |
|---|---|
HMAC_SHA256 | HMAC-SHA-256 |
HMAC_SHA384 | HMAC-SHA-384 |
HMAC_SHA512 | HMAC-SHA-512 |
Key
impl Key {
pub fn new(algorithm: Algorithm, key_bytes: &[u8]) -> Self;
pub fn algorithm(&self) -> Algorithm;
}
pub fn sign(key: &Key, data: &[u8]) -> Tag;
pub fn verify(key: &Key, data: &[u8], tag: &[u8]) -> Result<(), Unspecified>;
Key material is stored in SecretBuf and zeroed on drop via OPENSSL_cleanse.
hkdf — Key Derivation
use ring_native_ossl::hkdf::{self, Salt, HKDF_SHA256};
// Extract + expand (full HKDF)
let salt = Salt::new(HKDF_SHA256, b"random-salt");
let prk = salt.extract(b"input-key-material");
struct MyKeyType(usize);
impl hkdf::KeyType for MyKeyType {
fn len(&self) -> usize { self.0 }
}
let mut okm_bytes = [0u8; 32];
prk.expand(&[b"info"], &MyKeyType(32))
.unwrap()
.fill(&mut okm_bytes)
.unwrap();
Algorithms
| Static | Hash |
|---|---|
HKDF_SHA256 | HKDF-SHA-256 |
HKDF_SHA384 | HKDF-SHA-384 |
HKDF_SHA512 | HKDF-SHA-512 |
Types
impl Salt {
pub fn new(algorithm: Algorithm, value: &[u8]) -> Self;
pub fn extract(self, secret: &[u8]) -> Prk;
pub fn algorithm(&self) -> Algorithm;
}
impl Prk {
/// Construct a PRK directly from bytes (skips extract step).
pub fn new_less_safe(algorithm: Algorithm, value: &[u8]) -> Self;
/// Expand the PRK into output key material.
pub fn expand<'a, L: KeyType>(
&'a self,
info: &'a [&[u8]],
len: L,
) -> Result<Okm<'a, L>, Unspecified>;
}
impl<'a, L: KeyType> Okm<'a, L> {
pub fn len(&self) -> &L;
pub fn fill(self, out: &mut [u8]) -> Result<(), Unspecified>;
}
The KeyType trait lets callers express the desired output length as a typed
value (e.g. a newtype wrapping usize) so the length is checked at fill time.
aead — Authenticated Encryption
use ring_native_ossl::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM, NONCE_LEN};
let key_bytes = [0u8; 32];
let key = LessSafeKey::new(UnboundKey::new(&AES_256_GCM, &key_bytes).unwrap());
let nonce = Nonce::assume_unique_for_key([0u8; NONCE_LEN]);
// Encrypt (appends tag in-place)
let mut in_out = b"plaintext".to_vec();
key.seal_in_place_append_tag(nonce, Aad::empty(), &mut in_out).unwrap();
// Decrypt (removes tag in-place, returns plaintext slice)
let nonce = Nonce::assume_unique_for_key([0u8; NONCE_LEN]);
let plaintext = key.open_in_place(nonce, Aad::empty(), &mut in_out).unwrap();
Algorithms
| Static | Cipher | Key | Tag |
|---|---|---|---|
AES_128_GCM | AES-128-GCM | 16 bytes | 16 bytes |
AES_256_GCM | AES-256-GCM | 32 bytes | 16 bytes |
CHACHA20_POLY1305 | ChaCha20-Poly1305 | 32 bytes | 16 bytes |
UnboundKey and LessSafeKey
impl UnboundKey {
pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8])
-> Result<Self, Unspecified>;
pub fn algorithm(&self) -> &'static Algorithm;
}
impl LessSafeKey {
pub fn new(key: UnboundKey) -> Self;
pub fn algorithm(&self) -> &'static Algorithm;
/// Encrypt `in_out` in place; append the authentication tag.
pub fn seal_in_place_append_tag<A: AsRef<[u8]>>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &mut Vec<u8>,
) -> Result<(), Unspecified>;
/// Decrypt `in_out` in place; return a slice of the plaintext (within the buffer).
pub fn open_in_place<'in_out, A: AsRef<[u8]>>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &'in_out mut [u8],
) -> Result<&'in_out mut [u8], Unspecified>;
/// Decrypt only the bytes in `ciphertext_and_tag[src]`.
pub fn open_within<'in_out, A: AsRef<[u8]>>(
&self,
nonce: Nonce,
aad: Aad<A>,
in_out: &'in_out mut [u8],
src: std::ops::RangeFrom<usize>,
) -> Result<&'in_out mut [u8], Unspecified>;
}
Key material (UnboundKey.key, LessSafeKey.key) is stored in SecretBuf
and zeroed on drop.
Nonce construction
// From a fixed 12-byte array (caller guarantees uniqueness per key)
let nonce = Nonce::assume_unique_for_key([0u8; 12]);
// From a byte slice (checked length)
let nonce = Nonce::try_assume_unique_for_key(&bytes)?;
agreement — Ephemeral Key Exchange
use ring_native_ossl::agreement::{self, EphemeralPrivateKey, UnparsedPublicKey, X25519};
use ring_native_ossl::rand::SystemRandom;
let rng = SystemRandom::new();
let my_private = EphemeralPrivateKey::generate(&X25519, &rng).unwrap();
let my_public = my_private.compute_public_key().unwrap();
// The peer sends their raw public key bytes.
let peer_public = UnparsedPublicKey::new(&X25519, peer_pub_bytes);
let shared = agreement::agree_ephemeral(my_private, &peer_public, |key_material| {
// `key_material` is the raw shared secret; derive from it here.
key_material.to_vec()
}).unwrap();
Algorithms
| Static | Group | Public key |
|---|---|---|
X25519 | X25519 | 32 raw bytes |
ECDH_P256 | P-256 | 65 bytes (uncompressed 04‖x‖y) |
ECDH_P384 | P-384 | 97 bytes (uncompressed 04‖x‖y) |
EC public keys are validated for correct length and uncompressed-point format
(0x04 prefix) before the ECDH operation is attempted. Compressed points
(0x02/0x03) are rejected with PeerMisbehaved.
EphemeralPrivateKey
impl EphemeralPrivateKey {
pub fn generate(
alg: &'static Algorithm,
rng: &dyn SecureRandom,
) -> Result<Self, Unspecified>;
pub fn compute_public_key(&self) -> Result<PublicKey, Unspecified>;
pub fn algorithm(&self) -> &'static Algorithm;
}
EphemeralPrivateKey is consumed by agree_ephemeral — it cannot be reused
across multiple exchanges, matching ring’s design intent.
signature — Signing and Verification
Signing with ECDSA
use ring_native_ossl::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_ASN1};
// Generate a fresh keypair
let kp = EcdsaKeyPair::generate(&ECDSA_P256_SHA256_ASN1).unwrap();
let sig = kp.sign(b"message to sign").unwrap();
// Or load from PKCS#8 DER
let kp = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_ASN1, pkcs8_bytes).unwrap();
Signing with Ed25519
use ring_native_ossl::signature::Ed25519KeyPair;
let kp = Ed25519KeyPair::generate().unwrap();
let sig = kp.sign(b"message").unwrap();
Verification
use ring_native_ossl::signature::{self, UnparsedPublicKey, ECDSA_P256_SHA256_ASN1};
let pub_key = UnparsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, pub_key_bytes);
pub_key.verify(b"message", sig.as_ref()).unwrap();
// or free function
signature::verify(&ECDSA_P256_SHA256_ASN1, pub_key_bytes, b"message", sig.as_ref()).unwrap();
Algorithms
| Static | Key type | Hash | Encoding |
|---|---|---|---|
ECDSA_P256_SHA256_ASN1 | EC P-256 | SHA-256 | DER (ASN.1) |
ECDSA_P384_SHA384_ASN1 | EC P-384 | SHA-384 | DER (ASN.1) |
ED25519 | Ed25519 | internal | raw 64 bytes |
RSA_PKCS1_SHA256 | RSA | SHA-256 | PKCS#1 v1.5 |
RSA_PKCS1_SHA384 | RSA | SHA-384 | PKCS#1 v1.5 |
RSA_PKCS1_SHA512 | RSA | SHA-512 | PKCS#1 v1.5 |
RSA_PSS_SHA256 | RSA | SHA-256 | PSS (max salt) |
RSA_PSS_SHA384 | RSA | SHA-384 | PSS (max salt) |
RSA_PSS_SHA512 | RSA | SHA-512 | PSS (max salt) |
rand — Randomness
use ring_native_ossl::rand::{SecureRandom, SystemRandom};
let rng = SystemRandom::new();
let mut buf = [0u8; 32];
rng.fill(&mut buf).unwrap();
SystemRandom delegates to RAND_bytes via native_ossl::rand::Rand::fill.
error — Error Types
use ring_native_ossl::error::{KeyRejected, Unspecified};
| Type | ring equivalent | Used for |
|---|---|---|
Unspecified | ring::error::Unspecified | Any cryptographic failure |
KeyRejected | ring::error::KeyRejected | Invalid key material |
Both implement std::error::Error.
Limitations
The following ring APIs are not implemented:
ring::aead::SealingKey/OpeningKey— ring’s one-nonce-sequence API that enforces monotonic nonce advancement. UseLessSafeKeywith your own nonce counter instead.ring::signature::RsaKeyPair— RSA signing keypairs. RSA verification is fully supported. RSA signature generation can be done directly vianative_ossl::pkey::Signer.ring::signature::ECDSA_*_FIXED— fixed-size (P1363) ECDSA encoding. Only DER (ASN.1) encoding is provided.ring::pbkdf2— usenative_ossl::kdf::Pbkdf2Builderdirectly.ring::test— test utilities not applicable outside ring’s own test suite.