mirror of
https://github.com/borgbackup/borg.git
synced 2026-07-10 02:31:22 -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
d3a9799563
commit
710db85b20
9 changed files with 91 additions and 21 deletions
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
|
@ -568,8 +568,7 @@ jobs:
|
|||
pkgman install -y openssl3
|
||||
pkgman install -y rust_bin
|
||||
pkgman install -y python3.11
|
||||
pkgman install -y cffi
|
||||
pkgman install -y lz4_devel openssl3_devel libffi_devel
|
||||
pkgman install -y lz4_devel openssl3_devel
|
||||
|
||||
# there is no pkgman package for tox, so we install it into a venv
|
||||
python3 -m ensurepip --upgrade
|
||||
|
|
@ -655,7 +654,7 @@ jobs:
|
|||
|
||||
- name: Build python venv
|
||||
run: |
|
||||
# building cffi / argon2-cffi in the venv fails, so we try to use the system packages
|
||||
# building native extensions in the venv fails, so we try to use the system packages
|
||||
python -m venv --system-site-packages env
|
||||
. env/bin/activate
|
||||
# python -m pip install --upgrade pip
|
||||
|
|
|
|||
|
|
@ -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.11.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.
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ on widely used libraries providing them:
|
|||
- BLAKE3 is used via the blake3_ package (new repos, ``--id-hash blake3``).
|
||||
- BLAKE2b from Python's hashlib_ is only used to read legacy (borg < 2.0) repos.
|
||||
- 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.
|
||||
"jsonargparse>=4.47.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,lz4,openssl,rclone,python-msgpack,python-argon2_cffi,python-platformdirs,python,cython,python-setuptools,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko,rust,python-maturin}
|
||||
pacman -S --needed --noconfirm git mingw-w64-ucrt-x86_64-{toolchain,pkgconf,lz4,openssl,rclone,python-msgpack,python-platformdirs,python,cython,python-setuptools,python-wheel,python-build,python-pkgconfig,python-packaging,python-pip,python-paramiko,rust,python-maturin}
|
||||
|
||||
if [ "$1" = "development" ]; then
|
||||
pacman -S --needed --noconfirm mingw-w64-ucrt-x86_64-python-{pytest,pytest-benchmark,pytest-cov,pytest-xdist}
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -144,7 +144,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
|
||||
|
|
@ -156,7 +156,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)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ from ..logger import create_logger
|
|||
logger = create_logger()
|
||||
|
||||
from blake3 import blake3
|
||||
import argon2.low_level
|
||||
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import StableDict
|
||||
|
|
@ -508,17 +507,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_argon2(self, encrypted_key, passphrase):
|
||||
key = self.argon2(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ def long_to_bytes(x: int) -> bytes: ...
|
|||
def hmac_sha256(key: bytes, data: bytes) -> bytes: ...
|
||||
def blake2b_256(key: bytes, data: bytes) -> bytes: ...
|
||||
def blake2b_128(data: bytes) -> bytes: ...
|
||||
def argon2_hash(
|
||||
secret: bytes, salt: bytes, time_cost: int, memory_cost: int, parallelism: int, hash_len: int, type: str
|
||||
) -> bytes: ...
|
||||
|
||||
# Exception classes
|
||||
class CryptoError(Exception):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -825,3 +853,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