mirror of
https://github.com/borgbackup/borg.git
synced 2026-05-23 02:25:33 -04:00
use argon2 from openssl >= 3.2, drop argon2-cffi, fixes #7963
- src/borg/crypto/low_level.pyx: implement `argon2_hash` using OpenSSL's `EVP_KDF` API for ARGON2 (requires OpenSSL >= 3.2.0). - src/borg/crypto/key.py: switch to the native `argon2_hash` implementation, removing `argon2-cffi` dependency. - setup.py: require OpenSSL >= 3.2.0 for the crypto extension to ensure ARGON2 KDF support is available. - pyproject.toml: drop `argon2-cffi` dependency. - docs: update installation requirements and security documentation to reflect the transition to OpenSSL for Argon2.
This commit is contained in:
parent
ca3e88f5b1
commit
9fa76bc437
6 changed files with 85 additions and 18 deletions
|
|
@ -162,10 +162,10 @@ following dependencies first. For the libraries you will also need their
|
|||
development header files (sometimes in a separate `-dev` or `-devel` package).
|
||||
|
||||
* `Python 3`_ >= 3.10.0
|
||||
* OpenSSL_ >= 1.1.1 (LibreSSL will not work)
|
||||
* OpenSSL_ >= 3.2.0 (LibreSSL will not work)
|
||||
* libacl_ (which depends on libattr_)
|
||||
* liblz4_ >= 1.7.0 (r129)
|
||||
* libffi (required for argon2-cffi-bindings)
|
||||
|
||||
* pkg-config (cli tool) - Borg uses this to discover header and library
|
||||
locations automatically. Alternatively, you can also point to them via some
|
||||
environment variables, see setup.py.
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ on widely used libraries providing them:
|
|||
primitives implemented in libcrypto.
|
||||
- SHA-256, SHA-512 and BLAKE2b from Python's hashlib_ standard library module are used.
|
||||
- HMAC and a constant-time comparison from Python's hmac_ standard library module are used.
|
||||
- argon2 is used via argon2-cffi.
|
||||
- argon2 is used from OpenSSL (>= 3.2).
|
||||
|
||||
.. _Horton principle: https://en.wikipedia.org/wiki/Horton_Principle
|
||||
.. _length extension: https://en.wikipedia.org/wiki/Length_extension_attack
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ dependencies = [
|
|||
"packaging",
|
||||
"platformdirs >=3.0.0, <5.0.0; sys_platform == 'darwin'", # for macOS: breaking changes in 3.0.0.
|
||||
"platformdirs >=2.6.0, <5.0.0; sys_platform != 'darwin'", # for others: 2.6+ works consistently.
|
||||
"argon2-cffi",
|
||||
"shtab>=1.8.0",
|
||||
"backports-zstd; python_version < '3.14'", # for python < 3.14.
|
||||
"xxhash>=2.0.0",
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -139,7 +139,7 @@ if not on_rtd:
|
|||
)
|
||||
|
||||
if is_win32:
|
||||
crypto_ext_lib = lib_ext_kwargs(pc, "BORG_OPENSSL_PREFIX", "libcrypto", "libcrypto", ">=1.1.1", lib_subdir="")
|
||||
crypto_ext_lib = lib_ext_kwargs(pc, "BORG_OPENSSL_PREFIX", "libcrypto", "libcrypto", ">=3.2.0", lib_subdir="")
|
||||
elif is_openbsd:
|
||||
# Use OpenSSL (not LibreSSL) because we need AES-OCB via the EVP API. Link
|
||||
# it statically to avoid conflicting with shared libcrypto from the base
|
||||
|
|
@ -151,7 +151,7 @@ if not on_rtd:
|
|||
extra_objects=[os.path.join(openssl_prefix, "lib", openssl_name, "libcrypto.a")],
|
||||
)
|
||||
else:
|
||||
crypto_ext_lib = lib_ext_kwargs(pc, "BORG_OPENSSL_PREFIX", "crypto", "libcrypto", ">=1.1.1")
|
||||
crypto_ext_lib = lib_ext_kwargs(pc, "BORG_OPENSSL_PREFIX", "crypto", "libcrypto", ">=3.2.0")
|
||||
|
||||
crypto_ext_kwargs = members_appended(
|
||||
dict(sources=[crypto_ll_source]), crypto_ext_lib, dict(extra_compile_args=cflags)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ from ..logger import create_logger
|
|||
|
||||
logger = create_logger()
|
||||
|
||||
import argon2.low_level
|
||||
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import StableDict
|
||||
from ..helpers import Error, IntegrityError
|
||||
|
|
@ -466,17 +464,9 @@ class FlexiKey:
|
|||
parallelism = 1
|
||||
# 8 is the smallest value that avoids the "Memory cost is too small" exception
|
||||
memory_cost = 8
|
||||
type_map = {"i": argon2.low_level.Type.I, "d": argon2.low_level.Type.D, "id": argon2.low_level.Type.ID}
|
||||
key = argon2.low_level.hash_secret_raw(
|
||||
secret=passphrase.encode("utf-8"),
|
||||
hash_len=output_len_in_bytes,
|
||||
salt=salt,
|
||||
time_cost=time_cost,
|
||||
memory_cost=memory_cost,
|
||||
parallelism=parallelism,
|
||||
type=type_map[type],
|
||||
return low_level.argon2_hash(
|
||||
passphrase.encode("utf-8"), salt, time_cost, memory_cost, parallelism, output_len_in_bytes, type
|
||||
)
|
||||
return key
|
||||
|
||||
def decrypt_key_file_pbkdf2(self, encrypted_key, passphrase):
|
||||
key = self.pbkdf2(passphrase, encrypted_key.salt, encrypted_key.iterations, 32)
|
||||
|
|
|
|||
|
|
@ -88,6 +88,34 @@ cdef extern from "openssl/evp.h":
|
|||
int EVP_CTRL_AEAD_SET_TAG
|
||||
int EVP_CTRL_AEAD_SET_IVLEN
|
||||
|
||||
cdef extern from "openssl/kdf.h":
|
||||
ctypedef struct EVP_KDF:
|
||||
pass
|
||||
ctypedef struct EVP_KDF_CTX:
|
||||
pass
|
||||
EVP_KDF *EVP_KDF_fetch(void *ctx, const char *algorithm, const char *properties)
|
||||
void EVP_KDF_free(EVP_KDF *kdf)
|
||||
EVP_KDF_CTX *EVP_KDF_CTX_new(EVP_KDF *kdf)
|
||||
void EVP_KDF_CTX_free(EVP_KDF_CTX *ctx)
|
||||
|
||||
cdef extern from "openssl/params.h":
|
||||
ctypedef struct OSSL_PARAM:
|
||||
pass
|
||||
OSSL_PARAM OSSL_PARAM_construct_uint32(const char *key, uint32_t *buf)
|
||||
OSSL_PARAM OSSL_PARAM_construct_octet_string(const char *key, void *buf, size_t bsize)
|
||||
OSSL_PARAM OSSL_PARAM_construct_end()
|
||||
|
||||
cdef extern from "openssl/core_names.h":
|
||||
const char *OSSL_KDF_PARAM_THREADS
|
||||
const char *OSSL_KDF_PARAM_ARGON2_LANES
|
||||
const char *OSSL_KDF_PARAM_ITER
|
||||
const char *OSSL_KDF_PARAM_ARGON2_MEMCOST
|
||||
const char *OSSL_KDF_PARAM_SALT
|
||||
const char *OSSL_KDF_PARAM_PASSWORD
|
||||
|
||||
cdef extern from "openssl/kdf.h":
|
||||
int EVP_KDF_derive(EVP_KDF_CTX *ctx, unsigned char *key, size_t keylen, const OSSL_PARAM params[])
|
||||
|
||||
|
||||
import struct
|
||||
|
||||
|
|
@ -944,3 +972,53 @@ cdef class CSPRNG:
|
|||
|
||||
# Swap items[i] and items[j]
|
||||
items[i], items[j] = items[j], items[i]
|
||||
|
||||
|
||||
def argon2_hash(bytes secret, bytes salt, uint32_t time_cost, uint32_t memory_cost,
|
||||
uint32_t parallelism, uint32_t hash_len, type):
|
||||
cdef EVP_KDF *kdf = NULL
|
||||
cdef EVP_KDF_CTX *kctx = NULL
|
||||
cdef OSSL_PARAM params[8]
|
||||
cdef OSSL_PARAM *p = params
|
||||
cdef uint32_t threads = 1
|
||||
cdef bytes result
|
||||
cdef const char *alg_name
|
||||
cdef const unsigned char *secret_c = secret
|
||||
cdef const unsigned char *salt_c = salt
|
||||
|
||||
if type == "i" or type == b"i":
|
||||
alg_name = b"ARGON2I"
|
||||
elif type == "d" or type == b"d":
|
||||
alg_name = b"ARGON2D"
|
||||
elif type == "id" or type == b"id":
|
||||
alg_name = b"ARGON2ID"
|
||||
else:
|
||||
raise ValueError("Invalid argon2 type")
|
||||
|
||||
kdf = EVP_KDF_fetch(NULL, alg_name, NULL)
|
||||
if kdf == NULL:
|
||||
raise CryptoError("Argon2 KDF not found in OpenSSL (requires >= 3.2)")
|
||||
|
||||
kctx = EVP_KDF_CTX_new(kdf)
|
||||
if kctx == NULL:
|
||||
EVP_KDF_free(kdf)
|
||||
raise MemoryError("Failed to create KDF context")
|
||||
|
||||
p[0] = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &threads)
|
||||
p[1] = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, ¶llelism)
|
||||
p[2] = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &time_cost)
|
||||
p[3] = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memory_cost)
|
||||
p[4] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, <void *>salt_c, len(salt))
|
||||
p[5] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, <void *>secret_c, len(secret))
|
||||
p[6] = OSSL_PARAM_construct_end()
|
||||
|
||||
result = PyBytes_FromStringAndSize(NULL, hash_len)
|
||||
|
||||
if EVP_KDF_derive(kctx, <unsigned char *>result, hash_len, params) <= 0:
|
||||
EVP_KDF_CTX_free(kctx)
|
||||
EVP_KDF_free(kdf)
|
||||
raise CryptoError("EVP_KDF_derive failed")
|
||||
|
||||
EVP_KDF_CTX_free(kctx)
|
||||
EVP_KDF_free(kdf)
|
||||
return result
|
||||
|
|
|
|||
Loading…
Reference in a new issue