abcrypt_py/
lib.rs

1// SPDX-FileCopyrightText: 2022 Shun Sakai
2//
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! The `abcrypt-py` crate is the Python bindings for the `abcrypt` crate.
6
7#![doc(html_root_url = "https://docs.rs/abcrypt-py/0.3.0/")]
8// Lint levels of rustc.
9#![forbid(unsafe_code)]
10#![deny(missing_docs)]
11// Lint levels of Clippy.
12#![allow(clippy::redundant_pub_crate)]
13
14mod error;
15mod params;
16
17use std::borrow::Cow;
18
19use abcrypt::argon2::{self, Algorithm, Version};
20use pyo3::{
21    Bound, PyResult, exceptions::PyValueError, prelude::PyModuleMethods, pyclass, pyfunction,
22    pymethods, pymodule, types::PyModule, wrap_pyfunction,
23};
24
25use crate::error::Error;
26pub use crate::params::Params;
27
28/// Specifications of the abcrypt encrypted data format.
29#[derive(Clone, Copy, Debug)]
30#[pyclass]
31pub struct Format;
32
33#[pymethods]
34impl Format {
35    /// The number of bytes of the header.
36    #[classattr]
37    pub const HEADER_SIZE: usize = abcrypt::HEADER_SIZE;
38
39    /// The number of bytes of the MAC (authentication tag) of the ciphertext.
40    #[classattr]
41    pub const TAG_SIZE: usize = abcrypt::TAG_SIZE;
42}
43
44/// Encrypts `plaintext` and into a newly allocated `bytes`.
45///
46/// This uses the recommended Argon2 parameters according to the OWASP Password
47/// Storage Cheat Sheet. This also uses Argon2id as the Argon2 type and version
48/// 0x13 as the Argon2 version.
49///
50/// # Errors
51///
52/// Returns an error if the Argon2 context is invalid.
53#[inline]
54#[pyfunction]
55pub fn encrypt<'a>(plaintext: &[u8], passphrase: &[u8]) -> PyResult<Cow<'a, [u8]>> {
56    let ciphertext = abcrypt::encrypt(plaintext, passphrase).map_err(Error::from)?;
57    Ok(ciphertext.into())
58}
59
60/// Encrypts `plaintext` with the specified Argon2 parameters and into a newly
61/// allocated `bytes`.
62///
63/// This uses Argon2id as the Argon2 type and version 0x13 as the Argon2
64/// version.
65///
66/// # Errors
67///
68/// Returns an error if any of the following are true:
69///
70/// - The Argon2 parameters are invalid.
71/// - The Argon2 context is invalid.
72#[inline]
73#[pyfunction]
74pub fn encrypt_with_params<'a>(
75    plaintext: &[u8],
76    passphrase: &[u8],
77    memory_cost: u32,
78    time_cost: u32,
79    parallelism: u32,
80) -> PyResult<Cow<'a, [u8]>> {
81    let params = argon2::Params::new(memory_cost, time_cost, parallelism, None)
82        .map_err(|e| PyValueError::new_err(e.to_string()))?;
83    let ciphertext =
84        abcrypt::encrypt_with_params(plaintext, passphrase, params).map_err(Error::from)?;
85    Ok(ciphertext.into())
86}
87
88/// Encrypts `plaintext` with the specified Argon2 type, Argon2 version and
89/// Argon2 parameters and into a newly allocated `bytes`.
90///
91/// # Errors
92///
93/// Returns an error if any of the following are true:
94///
95/// - The Argon2 type is invalid.
96/// - The Argon2 version is invalid.
97/// - The Argon2 parameters are invalid.
98/// - The Argon2 context is invalid.
99#[inline]
100#[pyfunction]
101pub fn encrypt_with_context<'a>(
102    plaintext: &[u8],
103    passphrase: &[u8],
104    argon2_type: u32,
105    argon2_version: u32,
106    memory_cost: u32,
107    time_cost: u32,
108    parallelism: u32,
109) -> PyResult<Cow<'a, [u8]>> {
110    let argon2_type = match argon2_type {
111        0 => Ok(Algorithm::Argon2d),
112        1 => Ok(Algorithm::Argon2i),
113        2 => Ok(Algorithm::Argon2id),
114        t => Err(abcrypt::Error::InvalidArgon2Type(t)),
115    }
116    .map_err(Error::from)?;
117    let argon2_version =
118        Version::try_from(argon2_version).map_err(|e| PyValueError::new_err(e.to_string()))?;
119    let params = argon2::Params::new(memory_cost, time_cost, parallelism, None)
120        .map_err(|e| PyValueError::new_err(e.to_string()))?;
121    let ciphertext =
122        abcrypt::encrypt_with_context(plaintext, passphrase, argon2_type, argon2_version, params)
123            .map_err(Error::from)?;
124    Ok(ciphertext.into())
125}
126
127/// Decrypts `ciphertext` and into a newly allocated `bytes`.
128///
129/// # Errors
130///
131/// Returns an error if any of the following are true:
132///
133/// - `ciphertext` is shorter than 164 bytes.
134/// - The magic number is invalid.
135/// - The version number is the unsupported abcrypt version number.
136/// - The version number is the unrecognized abcrypt version number.
137/// - The Argon2 type is invalid.
138/// - The Argon2 version is invalid.
139/// - The Argon2 parameters are invalid.
140/// - The Argon2 context is invalid.
141/// - The MAC (authentication tag) of the header is invalid.
142/// - The MAC (authentication tag) of the ciphertext is invalid.
143#[inline]
144#[pyfunction]
145pub fn decrypt<'a>(ciphertext: &[u8], passphrase: &[u8]) -> PyResult<Cow<'a, [u8]>> {
146    let plaintext = abcrypt::decrypt(ciphertext, passphrase).map_err(Error::from)?;
147    Ok(plaintext.into())
148}
149
150/// A Python module implemented in Rust.
151#[pymodule]
152fn abcrypt_py(m: &Bound<'_, PyModule>) -> PyResult<()> {
153    m.add_function(wrap_pyfunction!(encrypt, m)?)?;
154    m.add_function(wrap_pyfunction!(encrypt_with_params, m)?)?;
155    m.add_function(wrap_pyfunction!(encrypt_with_context, m)?)?;
156    m.add_function(wrap_pyfunction!(decrypt, m)?)?;
157    m.add_class::<Params>()?;
158    m.add_class::<Format>()?;
159    Ok(())
160}