Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/sha-crypt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ jobs:
toolchain: ${{ matrix.rust }}
targets: ${{ matrix.target }}
- run: cargo build --target ${{ matrix.target }} --no-default-features
- run: cargo build --target ${{ matrix.target }} --no-default-features --features simple

minimal-versions:
if: false # disabled while using pre-releases
uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
with:
working-directory: ${{ github.workflow }}
working-directory: ${{ github.workflow }}

test:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions sha-crypt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ sha2 = { version = "0.11.0-rc.3", default-features = false }
base64ct = { version = "1.8", default-features = false, features = ["alloc"] }

# optional dependencies
getrandom = { version = "0.3", optional = true, default-features = false }
mcf = { version = "0.6.0-rc.0", optional = true, default-features = false, features = ["alloc", "base64"] }
password-hash = { version = "0.6.0-rc.4", optional = true, default-features = false }
subtle = { version = "2", optional = true, default-features = false }

[features]
default = ["simple"]
simple = ["dep:getrandom", "dep:mcf", "dep:subtle"]
getrandom = ["password-hash/getrandom", "simple"]
simple = ["dep:mcf", "dep:password-hash", "dep:subtle"]

[package.metadata.docs.rs]
all-features = true
10 changes: 2 additions & 8 deletions sha-crypt/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,8 @@ pub const BLOCK_SIZE_SHA256: usize = 32;
/// Block size for SHA512
pub const BLOCK_SIZE_SHA512: usize = 64;

/// PWD part length of the password string of SHA256
#[cfg(feature = "simple")]
pub const PW_SIZE_SHA256: usize = 43;

/// Maximum length of a salt
#[cfg(feature = "simple")]
pub const SALT_MAX_LEN: usize = 16;

/// Inverse encoding map for SHA512.
#[cfg(feature = "simple")]
#[rustfmt::skip]
pub const MAP_SHA512: [u8; 64] = [
42, 21, 0,
Expand Down Expand Up @@ -40,6 +33,7 @@ pub const MAP_SHA512: [u8; 64] = [
];

/// Inverse encoding map for SHA256.
#[cfg(feature = "simple")]
#[rustfmt::skip]
pub const MAP_SHA256: [u8; 32] = [
20, 10, 0,
Expand Down
82 changes: 8 additions & 74 deletions sha-crypt/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,95 +1,29 @@
//! Error types.

use alloc::string;
use core::fmt;

#[cfg(feature = "simple")]
use alloc::string::String;

/// Error type.
#[derive(Debug)]
pub enum CryptError {
pub enum Error {
/// Should be within range defs::ROUNDS_MIN < defs::ROUNDS_MIN
RoundsError,

/// RNG failed.
RandomError,

/// UTF-8 error.
StringError(string::FromUtf8Error),
}

impl From<string::FromUtf8Error> for CryptError {
fn from(e: string::FromUtf8Error) -> Self {
CryptError::StringError(e)
}
}

impl core::error::Error for CryptError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
CryptError::StringError(err) => Some(err),
_ => None,
}
}
}
impl core::error::Error for Error {}

impl fmt::Display for CryptError {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CryptError::RoundsError => write!(f, "rounds error"),
CryptError::RandomError => write!(f, "random error"),
CryptError::StringError(_) => write!(f, "string error"),
Error::RoundsError => write!(f, "rounds error"),
}
}
}

/// Errors which occur when verifying passwords.
#[cfg(feature = "simple")]
#[derive(Debug)]
pub enum CheckError {
/// Format is invalid.
InvalidFormat(String),

/// Cryptographic error.
Crypt(CryptError),

/// Password hash doesn't match (invalid password).
HashMismatch,
}

#[cfg(feature = "simple")]
impl core::error::Error for CheckError {}

#[cfg(feature = "simple")]
impl fmt::Display for CheckError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidFormat(e) => write!(f, "invalid format: {e}"),
Self::Crypt(e) => write!(f, "cryptographic error: {e}"),
Self::HashMismatch => write!(f, "hash mismatch"),
impl From<Error> for password_hash::Error {
fn from(err: Error) -> Self {
match err {
Error::RoundsError => password_hash::Error::ParamInvalid { name: "rounds" },
}
}
}

/// Decoding errors.
#[cfg(feature = "simple")]
#[derive(Debug)]
pub struct DecodeError;

#[cfg(feature = "simple")]
impl From<DecodeError> for CheckError {
fn from(_: DecodeError) -> CheckError {
CheckError::InvalidFormat("invalid B64".into())
}
}

#[cfg(feature = "simple")]
impl core::error::Error for DecodeError {}

#[cfg(feature = "simple")]
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "decode error")
}
}
110 changes: 28 additions & 82 deletions sha-crypt/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,5 @@
//! Pure Rust implementation of the [`SHA-crypt` password hash based on SHA-512][1],
//! a legacy password hashing scheme supported by the [POSIX crypt C library][2].
//!
//! Password hashes using this algorithm start with `$6$` when encoded using the
//! [PHC string format][3].
//!
//! # Usage
//!
//! ```
//! # #[cfg(feature = "simple")]
//! # {
//! use sha_crypt::{Sha512Params, sha512_simple, sha512_check};
//!
//! // First setup the Sha512Params arguments with:
//! // rounds = 10_000
//! let params = Sha512Params::new(10_000).expect("RandomError!");
//!
//! // Hash the password for storage
//! let hashed_password = sha512_simple("Not so secure password", &params);
//!
//! // Verifying a stored password
//! assert!(sha512_check("Not so secure password", &hashed_password).is_ok());
//! # }
//! ```
//!
//! [1]: https://www.akkadia.org/drepper/SHA-crypt.txt
//! [2]: https://en.wikipedia.org/wiki/Crypt_(C)
//! [3]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md

#![no_std]
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
Expand All @@ -36,33 +8,51 @@
#![deny(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms)]

//! # Usage
//!
#![cfg_attr(feature = "getrandom", doc = "```")]
#![cfg_attr(not(feature = "getrandom"), doc = "```ignore")]
//! # fn main() -> password_hash::Result<()> {
//! // NOTE: example requires `getrandom` feature is enabled
//!
//! use sha_crypt::{SHA512_CRYPT, PasswordHasher, PasswordVerifier};
//!
//! let password = b"pleaseletmein"; // don't actually use this as a password!
//! let password_hash = SHA512_CRYPT.hash_password(password)?;
//! assert!(password_hash.as_str().starts_with("$6$"));
//!
//! // verify password is correct for the given hash
//! SHA512_CRYPT.verify_password(password, &password_hash)?;
//! # Ok(())
//! # }
//! ```

// TODO(tarcieri): heapless support
#[macro_use]
extern crate alloc;

mod consts;
mod errors;
mod params;
#[cfg(feature = "simple")]
mod simple;

pub use crate::{
consts::{BLOCK_SIZE_SHA256, BLOCK_SIZE_SHA512},
errors::CryptError,
errors::Error,
params::{ROUNDS_DEFAULT, ROUNDS_MAX, ROUNDS_MIN, Sha256Params, Sha512Params},
};

#[cfg(feature = "simple")]
pub use crate::simple::{sha256_check, sha256_simple, sha512_check, sha512_simple};
pub use {
crate::simple::{SHA256_CRYPT, SHA512_CRYPT, ShaCrypt},
mcf::{self, PasswordHash},
password_hash::{self, CustomizedPasswordHasher, PasswordHasher, PasswordVerifier},
};

use alloc::{string::String, vec::Vec};
use base64ct::{Base64ShaCrypt, Encoding};
use alloc::vec::Vec;
use sha2::{Digest, Sha256, Sha512};

#[cfg(feature = "simple")]
pub use crate::errors::{CheckError, DecodeError};

use crate::consts::{MAP_SHA256, MAP_SHA512};

/// The SHA512 crypt function returned as byte vector
///
/// If the provided hash is longer than defs::SALT_MAX_LEN character, it will
Expand Down Expand Up @@ -261,50 +251,6 @@ pub fn sha256_crypt(
digest_c
}

/// Same as sha512_crypt except base64 representation will be returned.
///
/// # Arguments
/// - `password` - The password to process as a byte vector
/// - `salt` - The salt value to use as a byte vector
/// - `params` - The Sha512Params to use
/// **WARNING: Make sure to compare this value in constant time!**
///
/// # Returns
/// - `Ok(())` if calculation was successful
/// - `Err(errors::CryptError)` otherwise
pub fn sha512_crypt_b64(password: &[u8], salt: &[u8], params: &Sha512Params) -> String {
let output = sha512_crypt(password, salt, params);

let mut transposed = [0u8; BLOCK_SIZE_SHA512];
for (i, &ti) in MAP_SHA512.iter().enumerate() {
transposed[i] = output[ti as usize];
}

Base64ShaCrypt::encode_string(&transposed)
}

/// Same as sha256_crypt except base64 representation will be returned.
///
/// # Arguments
/// - `password` - The password to process as a byte vector
/// - `salt` - The salt value to use as a byte vector
/// - `params` - The Sha256Params to use
/// **WARNING: Make sure to compare this value in constant time!**
///
/// # Returns
/// - `Ok(())` if calculation was successful
/// - `Err(errors::CryptError)` otherwise
pub fn sha256_crypt_b64(password: &[u8], salt: &[u8], params: &Sha256Params) -> String {
let output = sha256_crypt(password, salt, params);

let mut transposed = [0u8; BLOCK_SIZE_SHA256];
for (i, &ti) in MAP_SHA256.iter().enumerate() {
transposed[i] = output[ti as usize];
}

Base64ShaCrypt::encode_string(&transposed)
}

fn produce_byte_seq(len: usize, fill_from: &[u8]) -> Vec<u8> {
let bs = fill_from.len();
let mut seq: Vec<u8> = vec![0; len];
Expand Down
Loading