암호화 프레임워크 (Crypto API)

Linux Crypto Framework(Crypto API)를 커널 암호 연산의 공통 추상화 계층으로 심층 분석합니다. skcipher/aead/hash/rng API 분류와 사용 패턴, synchronous vs asynchronous 요청 경로, 알고리즘 등록과 우선순위(Priority) 선택, AES-NI·ARM Crypto Extensions·QAT 등 하드웨어 가속 연동, scatterlist 기반 버퍼(Buffer) 처리, dm-crypt/IPsec/tls 등 상위 서브시스템 통합, FIPS 모드 고려사항, 성능 계측과 보안 검증 절차까지 실전 커널 암호화(Encryption) 운영에 필요한 핵심을 다룹니다.

전제 조건: 커널 보안어셈블리(Assembly) 문서를 먼저 읽으세요. Crypto API는 알고리즘 선택뿐 아니라 키 관리, 가속기 연동, 호출 문맥 제약까지 함께 고려해야 합니다.
일상 비유: 이 주제는 봉인 등급이 다른 금고 운용과 비슷합니다. 자물쇠 종류만 맞추는 것이 아니라 열쇠 관리와 출입 절차를 함께 설계해야 전체 보안이 성립합니다.

핵심 요약

  • 전제 결합 — 보안, 성능, 아키텍처 지식을 함께 적용합니다.
  • 경계 명확화 — API 경계와 ABI 영향 범위를 먼저 확인합니다.
  • 위험 관리 — UAF, race, side-effect 가능성을 우선 점검합니다.
  • 계측 기반 판단 — 추측 대신 데이터로 개선 여부를 판단합니다.
  • 점진 적용 — 실험 범위를 작게 시작해 단계적으로 확장합니다.

단계별 이해

  1. 가설 수립
    문제와 개선 목표를 수치로 정의합니다.
  2. 제약 분석
    호환성, 안정성, 보안 제약을 먼저 확인합니다.
  3. 실험 적용
    최소 변경으로 효과와 부작용을 측정합니다.
  4. 정식 반영
    검증된 변경만 문서화해 반영합니다.
관련 표준: FIPS 197 (AES), FIPS 180-4 (SHA), FIPS 198-1 (HMAC), NIST SP 800-38A~F (블록 암호 운용 모드), FIPS 140-3 (암호 모듈 인증) — 커널 Crypto Framework (Crypto API)가 구현하는 암호화 알고리즘 표준입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

Linux Crypto Framework 개요

Linux 커널의 Crypto Framework(Crypto API)는 암호화 알고리즘을 커널 내에서 사용할 수 있도록 하는 통합 프레임워크입니다. 소프트웨어 구현과 하드웨어 가속(AES-NI, ARM CE, QAT, CAAM, CPT 등)을 동일한 인터페이스로 제공하여, 알고리즘 사용자(IPsec, dm-crypt, kTLS 등)가 구현 방식을 신경 쓰지 않아도 됩니다.

어디에서 사용되는가

Linux Crypto Framework 주요 사용처 Crypto API 통합 암호화 프레임워크 IPsec (xfrm) VPN 터널 암호화 dm-crypt 디스크 암호화 kTLS 커널 TLS 오프로드 WireGuard 차세대 VPN fscrypt 파일 단위 암호화 모듈 서명 무결성 검증 AES-NI / ARM CE CPU 명령어 가속 SW generic 순수 C 구현 Intel QAT PCIe 가속기 NXP CAAM SoC 임베디드 Marvell CPT 네트워크 SoC virtio-crypto 가상 디바이스 Priority 기반 자동 선택 동일 알고리즘의 여러 구현 중 priority가 가장 높은 것을 자동 선택 → HW 가속이 투명하게 적용
Crypto API는 상위 소비자(IPsec, dm-crypt 등)와 하위 구현(SW, CPU 명령어, HW 가속기)을 분리하여 투명한 가속을 제공합니다
사용처사용하는 API대표 알고리즘데이터 규모
IPsec ESPaead (gcm, authenc)rfc4106(gcm(aes))패킷 단위 (~1500B)
dm-crypt (LUKS)skcipher (xts, cbc)xts(aes)섹터 단위 (512~4096B)
kTLSaead (gcm)gcm(aes)레코드 단위 (~16KB)
WireGuardaead + kpprfc7539(chacha20,poly1305)패킷 단위
fscrypt (ext4/f2fs)skcipher (xts, adiantum)xts(aes), adiantum블록 단위 (4096B)
모듈/펌웨어 서명sig + shashecdsa + sha256다이제스트 (32~64B)
IMA 무결성shashsha256파일 전체
TCP SYN Cookieshash (siphash)siphash헤더 필드 (수십 B)
eCryptfsskcipher + shashcbc(aes) + sha256파일 블록

Linux Crypto Framework 아키텍처 원리

Crypto Framework (Crypto API)는 알고리즘 구현과 사용을 분리하는 프레임워크 패턴으로 설계되어 있습니다. 핵심 개념은 세 가지입니다:

Crypto Framework 핵심 구조 사용자 IPsec, dm-crypt, kTLS tfm (Transform) 알고리즘 인스턴스+키 request 데이터+IV+콜백 algorithm SW 또는 HW 구현 알고리즘 선택 (Priority 기반) aesni_intel(400) > aes_generic(100) → H/W가 있으면 자동 선택
Crypto Framework: 사용자 → Transform(인스턴스) → Request(작업) → Algorithm(구현)

알고리즘 조합 (Template) 원리

Crypto Framework (Crypto API)의 강력한 특성 중 하나는 템플릿 기반 알고리즘 조합입니다. "cbc(aes)"에서 cbc는 운용 모드 템플릿이고 aes는 기본 블록 암호입니다. 커널은 이를 재귀적으로 해석하여:

ℹ️

알고리즘 이름 해석 예시: "authenc(hmac(sha256),cbc(aes))"는 다음과 같이 분해됩니다:

  • authenc — 인증+암호화 템플릿
  • hmac — MAC 템플릿
  • sha256 — 해시(Hash) 알고리즘
  • cbc — 블록 암호 모드 템플릿
  • aes — 블록 암호 알고리즘

이 구조 덕분에 새 블록 암호(예: SM4)를 추가하면 기존 모든 템플릿(cbc, gcm, xts 등)과 자동 조합됩니다.

💡

Fallback 메커니즘: H/W 가속기가 특정 키 크기나 입력 크기를 지원하지 못하면, 자동으로 priority가 낮은 S/W 구현으로 fallback됩니다. 이는 CRYPTO_ALG_NEED_FALLBACK 플래그와 crypto_alloc_*의 type/mask 매개변수로 제어됩니다.

알고리즘 유형

Crypto API는 8가지 알고리즘 유형을 지원하며, 각각 전용 API전용 request 구조체를 가집니다. 목적에 맞는 유형을 선택하는 것이 첫 번째 설계 결정입니다:

유형API예시동기/비동기주요 사용처상세 섹션
대칭 암호 (Cipher)crypto_skcipherAES-CBC, AES-XTS, ChaCha20양쪽디스크 암호화, VPN 터널대칭 암호 예제
AEADcrypto_aeadAES-GCM, ChaCha20-Poly1305양쪽IPsec ESP, kTLS, WireGuardAEAD
동기 해시crypto_shashSHA-256, HMAC, CRC32C동기만무결성 검증, 서명 전처리해시 예제
비동기 해시crypto_ahashSHA-256 (DMA 엔진)비동기HW 해시 가속기shash/ahash 구현
비대칭 암/복호화crypto_akcipherRSA양쪽키 포장, 인증서 암호화비대칭 암호
전자서명crypto_sigECDSA, RSA동기모듈 서명, 펌웨어 검증서명 API
키 합의 (KPP)crypto_kppECDH, DH, Curve25519양쪽TLS/IPsec 핸드셰이크KPP 예제
난수 생성 (RNG)crypto_rngDRBG, Jitter RNG동기FIPS DRBG, Nonce 생성난수 생성기
압축crypto_comp / acompLZ4, ZSTD, Deflate양쪽zswap, Btrfs, QAT 압축QAT 압축
💡

유형 선택 가이드: 데이터 기밀성만 필요하면 skcipher, 기밀성+무결성이 동시에 필요하면 aead, 무결성 검증만 필요하면 shash/ahash를 사용하세요. 새 프로토콜 설계에서는 거의 항상 AEAD를 선택하는 것이 권장됩니다. skcipher+별도 MAC 조합은 Padding Oracle 등 조합 취약점의 위험이 있습니다.

비대칭 암호와 공개키 연산

커널 Crypto Framework에서 비대칭 연산은 하나의 API로 뭉뚱그리지 않고 암/복호화, 서명/검증, 키 합의를 각각 다른 인터페이스로 분리합니다. 이 구분을 이해하지 못하면 RSA, ECDSA, ECDH를 같은 계층의 대체재처럼 오해하게 됩니다.

연산커널 API대표 알고리즘커널에서 자주 쓰는 위치
공개키 암호화 / 개인키 복호화crypto_akcipherRSA세션 키 포장, 키 블롭 보호, 인증서 기반 부트 체인
개인키 서명 / 공개키 검증crypto_sigECDSA, RSA모듈 서명, 펌웨어(Firmware) 검증, 무결성(Integrity) 검증
공유 비밀 계산crypto_kppECDH, DHTLS/IPsec 핸드셰이크, 세션 키 합의
실무 원칙: 비대칭 연산은 대량 데이터 암호화보다 대칭 세션 키를 안전하게 교환하거나, 서명으로 신뢰를 검증하는 데 쓰는 편이 일반적입니다. 큰 파일이나 네트워크 페이로드(Payload) 자체는 이후 skcipher 또는 aead로 처리하는 하이브리드 구성이 표준적입니다.
비대칭 연산의 역할 분리 RSA 암/복호화 송신자 공개키로 암호화 암호문 세션 키 블롭 수신자 개인키 복호화 대량 데이터 직접 처리용이 아님 서명 / 검증 개인키 서명 digest 입력 서명값 무결성 증명 공개키 검증 메시지 본문이 아니라 digest 기준 비교 ECDH 키 합의 Alice 개인키/공개키 Bob 개인키/공개키 공유 비밀 대칭 세션 키 파생 암호화는 기밀성, 서명은 신뢰 검증, 키 합의는 세션 키 생성이라는 서로 다른 목표를 가집니다.

중요한 차이: crypto_akcipher는 공개키로 암호화하고 개인키로 복호화하는 경로를 다루며, crypto_sig는 서명과 검증을 위해 별도 API를 사용합니다. crypto_kpp는 암/복호화가 아니라 양측이 동일한 공유 비밀을 계산하는 용도이므로, 의미상 완전히 다른 계층입니다.

키 형식 주의: 최신 커널 문서 기준으로 crypto_akcipher_set_pub_key(), crypto_akcipher_set_priv_key(), crypto_sig_set_pubkey(), crypto_sig_set_privkey()는 BER/DER 형태의 키와 알고리즘 파라미터를 기대합니다. 즉, 단순히 RSA modulus나 ECC 좌표만 raw 바이트로 넘기는 식의 코드는 바로 맞지 않을 수 있습니다.
Crypto API 관점의 비대칭 연산 흐름 AKCIPHER alloc -> set pub/priv key -> request crypto_alloc_akcipher("rsa") akcipher_request_set_crypt() crypto_akcipher_encrypt/decrypt() SIG alloc -> set pub/priv key -> direct sign/verify crypto_alloc_sig("ecdsa") crypto_sig_set_pubkey/privkey() crypto_sig_sign()/verify() KPP alloc -> set secret -> req in/out crypto_alloc_kpp("ecdh") crypto_kpp_set_secret() generate_public / shared_secret AKCIPHER와 KPP는 request 객체를 두고, SIG는 tfm에 직접 sign/verify를 호출하는 점이 실무 차이입니다.

RSA AKCIPHER 예제

공개키 암호화 API는 crypto_alloc_akcipher()로 tfm을 얻고, akcipher_request에 입출력(I/O) scatterlist를 연결한 뒤 crypto_akcipher_encrypt() 또는 crypto_akcipher_decrypt()를 호출하는 형태입니다. 공개키/개인키는 BER/DER로 인코딩된 형태가 필요하다는 점이 대칭 API와 가장 크게 다릅니다.

#include <crypto/akcipher.h>
#include <linux/scatterlist.h>

static int rsa_encrypt_keyblob(const void *pub_der, unsigned int pub_der_len,
                               const u8 *plain, unsigned int plain_len,
                               u8 *cipher, unsigned int *cipher_len)
{
    struct crypto_akcipher *tfm;
    struct akcipher_request *req;
    struct scatterlist src, dst;
    int ret;

    tfm = crypto_alloc_akcipher("rsa", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    ret = crypto_akcipher_set_pub_key(tfm, pub_der, pub_der_len);
    if (ret)
        goto out_free_tfm;

    req = akcipher_request_alloc(tfm, GFP_KERNEL);
    if (!req) {
        ret = -ENOMEM;
        goto out_free_tfm;
    }

    sg_init_one(&src, plain, plain_len);
    sg_init_one(&dst, cipher, *cipher_len);
    akcipher_request_set_crypt(req, &src, &dst, plain_len, *cipher_len);

    ret = crypto_akcipher_encrypt(req);
    if (!ret)
        *cipher_len = req->dst_len;

    akcipher_request_free(req);
out_free_tfm:
    crypto_free_akcipher(tfm);
    return ret;
}
AKCIPHER 요청 모델: 최신 커널 문서 기준으로 akcipher_requestsrc, dst, src_len, dst_len를 갖는 비동기 요청 객체입니다. 소프트웨어 구현은 즉시 완료될 수 있지만, 하드웨어 가속기나 backlog 환경을 고려하면 completion 경로를 염두에 두는 편이 안전합니다.

전자서명 API 예제

서명 API는 request 객체를 쓰지 않고 tfm에 직접 crypto_sig_sign() 또는 crypto_sig_verify()를 호출합니다. 상위 계층이 이미 해시를 계산해 둔 상태에서 digest에 대해 서명/검증을 수행하는 식으로 사용하는 경우가 많습니다.

#include <crypto/sig.h>

static int verify_firmware_digest(const void *pub_der, unsigned int pub_der_len,
                                  const u8 *sig, unsigned int sig_len,
                                  const u8 *digest, unsigned int digest_len)
{
    struct crypto_sig *tfm;
    int ret;

    tfm = crypto_alloc_sig("ecdsa", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    ret = crypto_sig_set_pubkey(tfm, pub_der, pub_der_len);
    if (ret)
        goto out_free_tfm;

    ret = crypto_sig_verify(tfm, sig, sig_len, digest, digest_len);

out_free_tfm:
    crypto_free_sig(tfm);
    return ret;
}

서명 출력 버퍼는 crypto_sig_maxsize()로 산정하고, 검증에 넘기는 digest 길이는 상위 프로토콜이 정한 해시 길이와 정확히 맞춰야 합니다. 특히 ECDSA는 곡선 종류와 DER 인코딩 길이에 따라 서명 길이가 가변적일 수 있으므로, 고정 길이 배열을 가정하면 구현이 깨질 수 있습니다.

KPP ECDH 예제

crypto_kpp는 암호문을 만드는 API가 아니라 공유 비밀을 계산하는 API입니다. ECDH에서는 개인키를 struct ecdh로 표현한 뒤 crypto_ecdh_encode_key()로 패킷(Packet) 형태로 포장해 crypto_kpp_set_secret()에 넘깁니다.

#include <crypto/kpp.h>
#include <crypto/ecdh.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>

static int ecdh_shared_secret(const u8 *priv, unsigned int priv_len,
                               const u8 *peer_pub, unsigned int peer_pub_len,
                               u8 *secret, unsigned int *secret_len)
{
    struct crypto_kpp *tfm;
    struct kpp_request *req;
    struct ecdh params = {
        .key = (char *)priv,
        .key_size = priv_len,
    };
    struct scatterlist src, dst;
    char *packed = NULL;
    unsigned int packed_len;
    int ret;

    tfm = crypto_alloc_kpp("ecdh", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    packed_len = crypto_ecdh_key_len(&params);
    packed = kmalloc(packed_len, GFP_KERNEL);
    if (!packed) {
        ret = -ENOMEM;
        goto out_free_tfm;
    }

    ret = crypto_ecdh_encode_key(packed, packed_len, &params);
    if (ret)
        goto out_free_key;

    ret = crypto_kpp_set_secret(tfm, packed, packed_len);
    if (ret)
        goto out_free_key;

    req = kpp_request_alloc(tfm, GFP_KERNEL);
    if (!req) {
        ret = -ENOMEM;
        goto out_free_key;
    }

    sg_init_one(&src, peer_pub, peer_pub_len);
    sg_init_one(&dst, secret, *secret_len);
    kpp_request_set_input(req, &src, peer_pub_len);
    kpp_request_set_output(req, &dst, *secret_len);

    ret = crypto_kpp_compute_shared_secret(req);
    if (!ret)
        *secret_len = req->dst_len;

    kpp_request_free(req);
out_free_key:
    kfree(packed);
out_free_tfm:
    crypto_free_kpp(tfm);
    return ret;
}
알고리즘강점제약커널에서 보통 맡는 역할
RSA폭넓은 호환성, AKCIPHER로 직관적키와 서명 크기가 크고 연산 비용이 큼키 포장, 인증서 기반 암/복호화, 일부 서명 검증(Signature Verification)
ECDSA짧은 키와 짧은 서명, 검증 비용 절감곡선/인코딩 관리가 필요모듈, 펌웨어, 이미지 무결성 검증
ECDH세션 키 합의에 효율적단독으로 기밀성이나 인증을 주지 않음TLS, IPsec, WireGuard 전단의 공유 비밀 생성

커널에서 지원하는 비대칭 알고리즘 전체 목록

알고리즘커널 이름API 유형키 크기서명/출력 크기용도HW 가속
RSArsaakcipher + sig2048~4096 비트키 길이와 동일키 포장, 서명 검증CAAM PKHA, CPT AE, QAT
ECDSA P-256ecdsa-nist-p256sig256 비트~72B (DER)모듈 서명, 펌웨어 검증CAAM, CPT AE
ECDSA P-384ecdsa-nist-p384sig384 비트~104B (DER)고보안 서명CAAM, CPT AE
ECDH P-256ecdh-nist-p256kpp256 비트32B (공유 비밀)TLS/IPsec 키 합의CAAM, CPT AE
ECDH P-384ecdh-nist-p384kpp384 비트48B (공유 비밀)고보안 키 합의CAAM
Curve25519curve25519kpp256 비트32B (공유 비밀)WireGuard, 현대 프로토콜SW 전용 (ARM NEON 최적화)
DHdhkpp2048~8192 비트그룹 크기와 동일레거시 IPsec IKEv1CAAM, QAT
SM2sm2sig + akcipher256 비트~72B (DER)중국 국가 표준일부 HiSilicon
ECRDSAecrdsasig256/512 비트64/128B러시아 GOST 표준SW 전용

TLS 핸드셰이크에서 비대칭 연산의 역할

비대칭 암호의 핵심 사용처는 대칭 세션 키를 안전하게 합의하는 것입니다. TLS 1.3 핸드셰이크를 예로 들면, ECDH(키 합의) → ECDSA(서명 검증) → HKDF(키 파생) → AES-GCM(대칭 암호화) 순서로 비대칭·대칭 연산이 결합됩니다:

TLS 1.3 핸드셰이크: 비대칭 → 대칭 전환 과정 클라이언트 서버 ① ClientHello (ECDH 공개키 포함) crypto_kpp: ECDH 키쌍 생성 ② ServerHello (ECDH 공개키 + 인증서) crypto_kpp: ECDH 키쌍 ③ 공유 비밀 계산 kpp: compute_shared_secret ③ 공유 비밀 계산 kpp: compute_shared_secret 양측이 동일한 공유 비밀(Z) 획득 ④ HKDF: Z → 세션 키 파생 ⑤ CertificateVerify (ECDSA 서명) crypto_sig: ECDSA 서명 crypto_sig: ECDSA 검증 ⑥ Application Data — AES-GCM 대칭 암호화 비대칭 연산 (①~⑤) — 느리지만 1회만 수행 대칭 연산 (⑥~) — 빠르고 모든 데이터에 적용 crypto_kpp(ECDH) → crypto_sig(ECDSA 검증) → crypto_hash(HKDF) → crypto_aead(AES-GCM) ← 커널 API 매핑
TLS 1.3은 ECDH로 공유 비밀을 합의하고, ECDSA로 서버를 인증한 뒤, HKDF로 세션 키를 파생하여 AES-GCM 대칭 암호화로 전환합니다

커널 모듈 서명 검증 파이프라인

커널 모듈 서명(CONFIG_MODULE_SIG)은 비대칭 암호의 대표적 커널 내 사용처입니다. 빌드 시 개인키로 모듈을 서명하고, 로드 시 내장 공개키로 검증합니다:

커널 모듈 서명 검증 파이프라인 빌드 시 (sign-file 도구) 모듈 (.ko) ELF 바이너리 SHA-256 해시 crypto_shash("sha256") 개인키 서명 RSA/ECDSA (sign-file) 서명된 모듈 .ko + 서명 부록 로드 시 (insmod / modprobe) 서명된 모듈 .ko + 서명 부록 서명 분리 module_sig_check() SHA-256 재계산 crypto_shash 공개키 검증 crypto_sig_verify(RSA/ECDSA) 로드 허용 로드 거부 공개키 소스 • .builtin_trusted_keys: 커널 빌드 시 내장 (certs/signing_key.pem) • .secondary_trusted_keys: 런타임 추가 가능 (서명 체인 필요)
모듈 서명은 빌드 시 해시+서명, 로드 시 해시 재계산+서명 검증의 2단계 파이프라인입니다

키 형식과 DER 인코딩

커널 Crypto API의 비대칭 연산은 키를 DER(Distinguished Encoding Rules) 형식으로 요구합니다. PEM(Base64) 형식이 아닌 바이너리 ASN.1/DER 형태이며, 알고리즘에 따라 구조가 다릅니다:

# ━━━ 키 형식 변환과 커널 호환성 ━━━

# RSA 공개키: PEM → DER 변환
openssl rsa -in rsa_priv.pem -pubout -outform DER -out rsa_pub.der
# → SubjectPublicKeyInfo(SPKI) 형태, crypto_akcipher_set_pub_key()에 직접 전달

# RSA 개인키: PEM → DER 변환 (PKCS#8)
openssl pkcs8 -in rsa_priv.pem -topk8 -nocrypt -outform DER -out rsa_priv.der
# → PKCS#8 형태, crypto_akcipher_set_priv_key()에 전달

# ECDSA P-256 키쌍 생성
openssl ecparam -name prime256v1 -genkey -noout -outform DER -out ec_priv.der
openssl ec -in ec_priv.der -inform DER -pubout -outform DER -out ec_pub.der
# → crypto_sig_set_pubkey()에 DER 형태 직접 전달

# 커널 모듈 서명용 키 생성 (커널 빌드 자동)
# certs/signing_key.pem — 빌드 시 자동 생성
# → .builtin_trusted_keys 키링에 X.509 인증서로 내장

# DER 키 구조 확인
openssl asn1parse -in rsa_pub.der -inform DER
#   0:d=0 hl=4 l=290 cons: SEQUENCE
#   4:d=1 hl=2 l=  13 cons:  SEQUENCE        ← AlgorithmIdentifier
#  19:d=1 hl=4 l=271 prim:  BIT STRING       ← 공개키 데이터

비대칭 연산 HW 가속

비대칭 연산은 대칭 연산에 비해 수백~수천 배 느리기 때문에, TLS 핸드셰이크가 많은 환경에서 HW 가속의 효과가 큽니다. 커널의 주요 HW 가속기별 지원 현황입니다:

가속기RSA SignRSA VerifyECDSA SignECDSA VerifyECDHDH드라이버
Intel QAT 4xxx~100K 2048~1M 2048---~100Kqat_4xxx
AMD CCP~10K 2048~100K 2048~8K P-256~15K P-256~15K~10Kccp
NXP CAAM (PKHA)~2K 2048~20K 2048~4K P-256~8K P-256~8K~2Kcaampkc
Marvell CPT AE~50K 2048~200K 2048~80K P-256~100K P-256~80K~50Kotx2_cpt
ARM CryptoCell~1K 2048~5K 2048~2K P-256~4K P-256~4K-ccree
SW (커널 RSA/ECC)~500 2048~10K 2048~2K P-256~5K P-256~5K~500rsa-generic
💡

RSA Sign vs Verify 성능 차이: RSA 서명(개인키 연산)은 CRT(Chinese Remainder Theorem) 최적화를 사용해도 검증(공개키 연산)의 10~50배 느립니다. 공개키 지수 e=65537이 작아 검증은 빠르지만, 개인키 지수 d는 크기 때문입니다. 이것이 서버 측 SSL 핸드셰이크에서 HW 가속이 특히 중요한 이유입니다.

ECDSA vs RSA 선택 기준: 동일 보안 수준(128비트)에서 ECDSA P-256(32B 키)은 RSA-3072(384B 키)보다 키·서명 크기가 10배 작고 검증도 빠릅니다. 새 시스템에서는 ECDSA를 권장하며, 커널 모듈 서명도 6.x부터 ECDSA를 기본으로 지원합니다.

Curve25519 vs NIST P-256: Curve25519는 상수 시간 구현이 간단하여 부채널 공격에 강하고, 성능도 우수합니다. WireGuard가 Curve25519를 선택한 이유입니다. 다만 FIPS 인증이 필요한 환경에서는 NIST P-256을 사용해야 합니다.

비대칭 연산 성능 특성

연산SW (단일 코어)CAAM PKHACPT AEQAT비고
RSA-2048 Sign~1 ms~0.5 ms~0.02 ms~0.01 msCRT 최적화 포함
RSA-2048 Verify~0.05 ms~0.05 ms~0.005 ms~0.001 mse=65537, 빠름
RSA-4096 Sign~8 ms~4 ms~0.1 ms~0.05 ms키 길이 2× → 연산 8×
ECDSA P-256 Sign~0.3 ms~0.25 ms~0.012 ms-RSA-3072 동등 보안
ECDSA P-256 Verify~0.5 ms~0.12 ms~0.01 ms-서명보다 느림 (RSA와 반대)
ECDH P-256~0.3 ms~0.12 ms~0.012 ms-TLS 핸드셰이크 병목
X25519 (Curve25519)~0.05 ms---SW만, ARM NEON 최적화
ℹ️

관련 섹션 연결: AKCIPHER/KPP 드라이버 구현 상세는 AKCIPHER 드라이버 구현KPP 드라이버 구현, 펌웨어 검증 파이프라인 코드는 펌웨어 검증 파이프라인, FIPS 요구사항과 최소 키 길이는 FIPS 모드와 자가 테스트, 하드웨어 오프로드 선택 기준은 암호화 오프로드 결정 가이드를 함께 참고하세요.

해시 사용 예제

해시(Hash)는 임의 길이 데이터를 고정 길이 다이제스트(Digest)로 축약하는 일방향 함수입니다. Crypto API는 shash(동기 해시)ahash(비동기 해시) 두 인터페이스를 제공합니다. 대부분의 경우 shash가 간결하고 충분하며, HW DMA 해시 엔진을 사용할 때만 ahash가 필요합니다:

인터페이스API동기/비동기입력 형태적합 상황
shashcrypto_shash동기가상 주소 (vaddr)SW 해시, CPU 명령어 가속 (SHA-NI)
ahashcrypto_ahash비동기scatterlist (DMA 가능)HW DMA 해시 엔진, 대용량 분산 버퍼
ℹ️

shash vs ahash 선택: 커널 내부적으로 shash를 ahash로 자동 래핑하므로, ahash 인터페이스에서도 shash 구현이 사용될 수 있습니다. SW 전용이면 shash, HW DMA 엔진이면 ahash를 선택하세요. 예제 코드는 개념 설명용이며, 실제 커널 트리 구현(crypto/sha256_generic.c 등)을 함께 참고하세요.

#include <crypto/hash.h>
#include <linux/slab.h>

static int calc_sha256(const u8 *data, unsigned int len, u8 *digest)
{
    struct crypto_shash *tfm;
    struct shash_desc *desc;
    int ret;

    tfm = crypto_alloc_shash("sha256", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL);
    if (!desc) {
        crypto_free_shash(tfm);
        return -ENOMEM;
    }

    desc->tfm = tfm;
    ret = crypto_shash_digest(desc, data, len, digest);
    kfree(desc);
    crypto_free_shash(tfm);
    return ret;
}

/* 스택 할당 대안: SHASH_DESC_ON_STACK 매크로 사용
 * 힙 할당 오버헤드 없이 스택에 shash_desc를 직접 배치합니다.
 * 단, descsize가 크면 스택 사용량 초과 위험이 있으므로
 * CONFIG_FRAME_WARN을 초과하지 않는 작은 알고리즘에 적합합니다. */
static int calc_sha256_stack(const u8 *data, unsigned int len, u8 *digest)
{
    struct crypto_shash *tfm = crypto_alloc_shash("sha256", 0, 0);
    SHASH_DESC_ON_STACK(desc, tfm);  /* 스택에 shash_desc 할당 */
    int ret;

    if (IS_ERR(tfm))
        return PTR_ERR(tfm);
    desc->tfm = tfm;
    ret = crypto_shash_digest(desc, data, len, digest);
    shash_desc_zero(desc);          /* 민감 데이터 스택 소거 */
    crypto_free_shash(tfm);
    return ret;
}

대칭 암호 예제

대칭 암호(skcipher)는 동일한 키로 암호화와 복호화를 수행합니다. crypto_skcipher API는 블록 암호 모드(CBC, CTR, XTS 등)와 스트림 암호(ChaCha20)를 통합 인터페이스로 제공합니다. 4단계 라이프사이클을 따릅니다:

skcipher API 4단계 사용 흐름 ① tfm 할당 + 키 crypto_alloc_skcipher() crypto_skcipher_setkey() ② req 할당 + 설정 skcipher_request_alloc() set_callback() + set_crypt() ③ 암호화/복호화 crypto_skcipher_encrypt() 또는 _decrypt() ④ 해제 skcipher_request_free() crypto_free_skcipher() 장수 객체 (캐시) 요청마다 새로 or 재사용 동기(0) or 비동기(-EINPROGRESS) 콜백 완료 후에만 해제 tfm은 소켓/inode 수명에 맞춰 캐시, req는 in-flight 단위로 관리, IV는 요청마다 복사본 사용
tfm(장수 캐시) → req(요청 단위) → encrypt/decrypt(실행) → free(해제)의 4단계를 따릅니다
#include <crypto/skcipher.h>
#include <linux/scatterlist.h>

/* ━━━ AES-CBC 대칭 암호화 전체 예제 (비동기 지원) ━━━ */
static int aes_cbc_encrypt(const u8 *key, unsigned int key_len,
                           u8 *iv, u8 *data, unsigned int data_len)
{
    struct crypto_skcipher *tfm;
    struct skcipher_request *req;
    struct scatterlist sg;
    u8 iv_local[16];
    int ret;
    DECLARE_CRYPTO_WAIT(wait);

    /* ① tfm 할당 — 커널이 최적 구현을 자동 선택 */
    tfm = crypto_alloc_skcipher("cbc(aes)", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    ret = crypto_skcipher_setkey(tfm, key, key_len);
    if (ret)
        goto out_free_tfm;

    /* ② req 할당 + 콜백 설정 */
    req = skcipher_request_alloc(tfm, GFP_KERNEL);
    if (!req) { ret = -ENOMEM; goto out_free_tfm; }

    skcipher_request_set_callback(req,
        CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
        crypto_req_done, &wait);

    /* ③ scatterlist + IV 설정 (in-place 암호화) */
    sg_init_one(&sg, data, data_len);
    memcpy(iv_local, iv, 16);  /* IV 복사본 — 일부 드라이버가 원본 수정 */
    skcipher_request_set_crypt(req, &sg, &sg, data_len, iv_local);

    /* ④ 암호화 수행 — 동기·비동기 투명 처리 */
    ret = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);

    memzero_explicit(iv_local, sizeof(iv_local));
    skcipher_request_free(req);
out_free_tfm:
    crypto_free_skcipher(tfm);
    return ret;  /* 0: 성공, 음수: 에러 */
}
주요 대칭 암호 모드커널 이름패딩 필요병렬화주요 사용처
CBC (Cipher Block Chaining)cbc(aes)예 (블록 정렬)decrypt만레거시 IPsec, dm-crypt
CTR (Counter)ctr(aes)아니오완전 병렬GCM 내부, 키 유도
XTS (XEX Tweakable)xts(aes)부분 (CTS)완전 병렬dm-crypt, fscrypt
ECB (Electronic Codebook)ecb(aes)완전 병렬키 래핑, 테스트용
ChaCha20 (스트림)chacha20아니오완전 병렬WireGuard 내부
Adiantumadiantum(xchacha12,aes)아니오블록 단위저사양 디바이스 fscrypt
💡

모드 선택 가이드: 디스크 암호화는 xts(aes)(섹터 단위 무결성 불필요) 또는 AEAD(무결성 필요 시), 네트워크는 AEAD(gcm(aes))가 표준입니다. CBC는 새 프로토콜에서 사용하지 마세요 — Padding Oracle 취약점의 근본 원인이 됩니다. 전체 비동기 처리 패턴은 비동기 암호화, AEAD 사용법은 AEAD를 참고하세요.

하드웨어 가속

Crypto API의 priority 기반 자동 선택 메커니즘을 통해, 사용자 코드 변경 없이 하드웨어 가속이 투명하게 적용됩니다. 동일 알고리즘의 여러 구현이 등록되면 priority가 가장 높은 것이 자동 선택됩니다:

Priority 기반 알고리즘 자동 선택 crypto_alloc_skcipher("cbc(aes)") 사용자는 알고리즘 이름만 지정 crypto_find_alg() Priority 정렬 탐색 ① caam-cbc-aes (pri=3000) ② cbc-aes-aesni (pri=400) ③ cbc-aes-generic (pri=100) CAAM 자동 선택 (최고 priority) Fallback 자동 전환 CAAM이 지원하지 않는 키 크기/모드 → AES-NI(400) → generic(100) 순서로 시도 Priority 척도: generic(100) → AES-NI/ARM CE(300~400) → CAAM(3000) → CPT(4001) → QAT(4001+) — 높을수록 우선
사용자 코드는 "cbc(aes)"만 요청하고, 커널이 priority 순서로 최적 구현을 자동 선택합니다
가속 유형대표 하드웨어Priority 범위특징상세 섹션
CPU ISA 명령어AES-NI, SHA-NI, ARM CE, CRC32C300~400최저 지연, 소량 데이터 최적, CPU 코어 점유AES-NI, ARM 가속
PCI 가속기Intel QAT, AMD CCP, Marvell NITROX200~4001고처리량, PCIe DMA, 배치 처리, CPU 부하 분산QAT, CCP
SoC 임베디드NXP CAAM, Marvell CPT, HiSilicon SEC3000~4001SoC 내장, 초저지연, 임베디드/네트워크 특화CAAM, CPT
가상 디바이스virtio-crypto가변VM에서 호스트 가속기 활용virtio-crypto
# ━━━ 하드웨어 가속 상태 확인 ━━━

# 등록된 모든 알고리즘과 드라이버·우선순위 확인
grep -E "^name|^driver|^priority|^type" /proc/crypto | head -20
# name         : cbc(aes)
# driver       : cbc-aes-aesni      ← AES-NI 가속
# priority     : 400
# type         : skcipher

# CPU 암호 명령어 지원 확인
grep -oE "aes|sha_ni|pclmulqdq|crc32c" /proc/cpuinfo | sort -u
# aes pclmulqdq sha_ni crc32c

# ARM CE 확인
grep -oE "aes|sha2|pmull|crc32" /proc/cpuinfo | sort -u

# HW 가속기 모듈 로드 확인
lsmod | grep -E "aesni|caam|qat|otx2_cpt|hisi_sec"
ℹ️

투명한 가속의 의미: crypto_alloc_skcipher("cbc(aes)", 0, 0)를 호출하는 코드는 AES-NI가 있는 x86 서버에서든, CAAM이 있는 i.MX 보드에서든, 가속기 없는 RISC-V에서든 동일하게 동작합니다. 커널이 사용 가능한 최적 구현을 자동 선택하므로, 사용자 코드에 하드웨어 종속적인 분기가 불필요합니다.

비동기 암호화 (Async Crypto)

커널 Crypto API의 비동기(Asynchronous) 처리 모델은 HW 가속기와 CPU를 병렬로 동작시키는 핵심 메커니즘입니다. 동기(synchronous) 알고리즘(AES-NI 등)은 함수가 반환되면 이미 완료되지만, 비동기 알고리즘(QAT, CAAM, CPT 등)은 요청을 큐에 제출한 뒤 즉시 반환하고, HW가 완료되면 콜백(callback)으로 결과를 통지합니다. 이 차이를 이해하지 않으면 데이터 손상, deadlock, use-after-free 등 치명적 버그가 발생합니다.

동기 vs 비동기 처리 모델

Crypto API의 모든 알고리즘은 /proc/cryptoasync 필드로 동기/비동기 여부를 확인할 수 있습니다. 동일한 알고리즘 이름(cbc(aes) 등)에 대해 동기·비동기 구현이 공존하며, priority가 높은 구현이 자동 선택됩니다:

동기(Sync) vs 비동기(Async) 처리 타임라인 동기 (AES-NI / ARM CE) /proc/crypto → async: no CPU 코어 호출 CPU가 직접 암호 연산 (블로킹) 완료 다음 작업 가능 ← ret = 0 (즉시 완료) → 비동기 (QAT / CAAM / CPT) /proc/crypto → async: yes CPU 코어 제출 CPU 자유 — 다른 패킷 처리 가능 콜백 실행 다음 작업 HW 엔진 DMA → HW 암호 연산 → DMA 출력 IRQ ← ret = -EINPROGRESS → 콜백 핵심 차이 동기: ret = 0 → 이미 완료. 콜백 호출 없음. CPU가 연산 시간 동안 블로킹. 비동기: ret = -EINPROGRESS → 아직 미완료. 콜백으로 결과 통지. CPU는 즉시 다른 작업 가능. 비동기+백로그: ret = -EBUSY → 큐 포화로 백로그 대기열에 추가. 결국 콜백으로 완료 통지. 규칙: 0 반환이면 콜백 절대 없음. -EINPROGRESS/-EBUSY이면 콜백이 반드시 와야 함.
동기 모델은 CPU가 직접 연산하여 즉시 완료하지만, 비동기 모델은 HW에 위임하고 CPU가 다른 일을 하다가 콜백으로 결과를 받습니다
# /proc/crypto에서 동기·비동기 구분 확인
grep -A4 "^name.*cbc(aes)" /proc/crypto
# name         : cbc(aes)
# driver       : cbc-aes-aesni        ← AES-NI (동기)
# priority     : 400
# async        : no                   ← 동기: 즉시 완료
# ---
# name         : cbc(aes)
# driver       : cbc-aes-caam         ← CAAM (비동기)
# priority     : 3000
# async        : yes                  ← 비동기: 콜백으로 완료

# CAAM priority(3000) > AES-NI priority(400)
# → i.MX/Layerscape에서 crypto_alloc_skcipher("cbc(aes)")는 CAAM을 자동 선택

반환값 계약과 완료 통지

Crypto API 비동기 모델에서 가장 중요한 것은 반환값과 콜백의 관계입니다. crypto_skcipher_encrypt(), crypto_aead_encrypt(), crypto_ahash_digest() 등 모든 비동기 API 함수는 동일한 반환값 규약을 따릅니다:

반환값의미콜백 호출 여부호출자가 해야 할 일
0즉시 완료 (동기적으로 처리됨)호출 안 됨결과 데이터를 바로 사용
-EINPROGRESSHW에 제출됨, 나중에 완료반드시 호출됨콜백에서 결과 처리 (req 접근 금지)
-EBUSY큐 포화, 백로그(backlog)에 추가반드시 호출됨 (2회)첫 콜백(err=-EINPROGRESS)=큐 진입, 두 번째 콜백=완료
-EINVAL파라미터 오류 (키/IV/길이)호출 안 됨에러 처리
-ENOMEM메모리 할당 실패호출 안 됨에러 처리 또는 재시도
-EBADMSGAEAD 인증 태그 불일치상황에 따라 다름동기 실패면 직접 처리, 비동기면 콜백에서 처리
⚠️

가장 흔한 치명적 버그 3가지:

① 0 반환 후 콜백 호출: 이미 완료된 요청에 대해 콜백이 다시 불리면 상위 계층이 중복 완료(double completion)로 패닉하거나 데이터를 손상시킵니다.

② -EINPROGRESS 반환 후 req 즉시 해제: HW가 아직 DMA로 req 메모리에 접근 중인데 해제하면 use-after-free가 발생합니다. 콜백이 올 때까지 req를 유지해야 합니다.

③ -EBUSY 무시: 백로그 콜백을 처리하지 않으면 요청이 영원히 완료되지 않아 메모리 누수와 deadlock이 발생합니다.

콜백 메커니즘 상세

비동기 콜백은 skcipher_request_set_callback() / aead_request_set_callback() / ahash_request_set_callback()으로 등록합니다. 콜백 함수의 시그니처와 호출 컨텍스트를 이해하는 것이 안전한 비동기 코드 작성의 핵심입니다:

비동기 콜백 호출 흐름 (정상 경로 vs 백로그 경로) 정상 경로 (ret = -EINPROGRESS) 호출자 (process ctx) encrypt(req) 제출 crypto_queue → HW 제출 HW 엔진 처리 DMA 암호 연산 IRQ 콜백(err=0) softirq/tasklet 컨텍스트 백로그 경로 (ret = -EBUSY) 호출자 (process ctx) encrypt(req) → -EBUSY 백로그 큐 큐 포화 → 대기열 슬롯 확보 1차 콜백 err = -EINPROGRESS HW 처리 DMA 2차 콜백 err = 0 콜백 플래그(flags)와 동작 제어 CRYPTO_TFM_REQ_MAY_BACKLOG 큐가 포화해도 요청을 거부하지 않고 백로그 대기열에 추가합니다. 이 플래그 없이 큐가 가득 차면 -ENOSPC를 반환하고 콜백 없이 실패합니다. CRYPTO_TFM_REQ_MAY_SLEEP 콜백이 호출될 때까지 호출자가 sleep 가능한 process context임을 알립니다. softirq/atomic 컨텍스트에서는 이 플래그를 사용하면 안 됩니다. 콜백 호출 컨텍스트 HW 가속기: 콜백은 보통 softirq/tasklet/workqueue에서 호출됩니다 (IRQ 핸들러 아님). SW fallback: 콜백이 encrypt() 함수 내부에서 동기적으로 호출될 수 있습니다 (0 반환 시 안 불림). 콜백 안에서 GFP_KERNEL 할당, mutex, 또는 sleep 가능한 함수를 호출하면 안 됩니다.
정상 경로는 콜백이 1회, 백로그 경로는 큐 진입·완료 총 2회 콜백이 호출됩니다
/* ━━━ 비동기 콜백 함수 작성 패턴 ━━━ */

/* 콜백 함수 시그니처 — 모든 비동기 API 공통 */
static void my_crypto_done(void *data, int err)
{
    struct my_async_result *result = data;

    /* 백로그 경로: 1차 콜백은 "큐 진입 알림"일 뿐 */
    if (err == -EINPROGRESS)
        return;  /* 아직 완료 아님, 2차 콜백을 기다림 */

    /* 여기가 진짜 완료 지점 — err=0이면 성공, 음수면 에러 */
    result->err = err;
    complete(&result->completion);
}

/* 콜백 결과를 전달받을 구조체 */
struct my_async_result {
    struct completion completion;
    int err;
};

crypto_wait_req() — 비동기를 동기처럼 사용하기

process context(sleep 가능)에서는 콜백 기반 상태 머신을 직접 구현할 필요 없이 DECLARE_CRYPTO_WAITcrypto_wait_req()를 사용하면 동기·비동기 양쪽을 동일한 코드로 처리할 수 있습니다. 이 패턴은 dm-crypt, fscrypt, IKE 등 커널 내 대부분의 Crypto API 소비자가 사용합니다:

crypto_wait_req() 내부 동작 흐름 crypto_wait_req(ret, &wait) ret = encrypt() 반환값 ret == -EINPROGRESS? Yes wait_for_completion() sleep until 콜백 도착 콜백의 err 반환 No ret 그대로 반환 0(성공) 또는 에러 사용 패턴 (3줄이면 충분) DECLARE_CRYPTO_WAIT(wait); skcipher_request_set_callback(req, MAY_BACKLOG|MAY_SLEEP, crypto_req_done, &wait); ret = crypto_wait_req(encrypt(req), &wait);
crypto_wait_req()는 -EINPROGRESS면 sleep하고, 0이나 에러면 즉시 반환하여 동기·비동기를 투명하게 처리합니다
/* ━━━ crypto_wait_req() 사용 예 — 가장 간결한 비동기 패턴 ━━━ */

#include <crypto/skcipher.h>

static int encrypt_data(struct crypto_skcipher *tfm,
                       u8 *data, unsigned int len, u8 *iv)
{
    struct skcipher_request *req;
    struct scatterlist sg;
    DECLARE_CRYPTO_WAIT(wait);  /* completion + err 포함 */
    int ret;

    req = skcipher_request_alloc(tfm, GFP_KERNEL);
    if (!req)
        return -ENOMEM;

    sg_init_one(&sg, data, len);
    skcipher_request_set_crypt(req, &sg, &sg, len, iv);
    skcipher_request_set_callback(req,
        CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
        crypto_req_done, &wait);     /* 커널 내장 콜백 */

    /* 한 줄로 동기·비동기 모두 처리 */
    ret = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
    /* ret == 0: 성공 (동기든 비동기든 무관) */
    /* ret < 0: 에러 (암호화 실패) */

    skcipher_request_free(req);
    return ret;
}

비동기 요청 라이프사이클

비동기 암호 요청은 4단계 라이프사이클을 거칩니다. 각 단계에서 메모리 소유권이 이동하므로 순서를 지키는 것이 중요합니다:

비동기 요청 객체 라이프사이클 ① Alloc skcipher_request_alloc() set_callback() set_crypt() ② Submit crypto_skcipher_encrypt() ret = -EINPROGRESS req 접근 금지! ③ In-flight HW가 DMA로 처리 중 CPU는 다른 작업 수행 req 해제 금지! ④ Complete 콜백(err=0) 도착 결과 데이터 사용 가능 req 해제 OK 메모리 소유권: ① 호출자 → ② 드라이버/HW → ③ HW (DMA) → ④ 호출자에게 복귀 주요 객체 수명 규칙 tfm (crypto_skcipher 등) 장수 객체. 소켓/inode 수명에 맞춰 캐시. 여러 req가 공유 가능. req (skcipher_request 등) in-flight 단위. 동시 요청 N개면 req도 N개 필요. 재사용은 완료 후에만. scatterlist (src/dst) HW가 DMA로 접근하므로, in-flight 중 해제·수정 절대 금지. IV 버퍼 일부 드라이버가 in-place 수정함. 요청마다 복사본 사용 권장. 키 (setkey 후) tfm 내부에 저장됨. 키 변경은 모든 in-flight req 완료 후에만 안전. 핵심: -EINPROGRESS 반환 후 ~ 콜백 도착 전 구간에서 req/sg/IV를 건드리면 UB(Undefined Behavior)
제출(-EINPROGRESS) 후부터 콜백 도착까지 req와 관련 버퍼의 소유권은 드라이버/HW에 있습니다

완료 통지 모델 3종 비교

HW 가속기가 암호 연산을 완료한 뒤 결과를 CPU에 통지하는 방식은 3가지입니다. 워크로드 특성에 따라 최적 모델이 달라집니다:

항목동기 (Synchronous)비동기 인터럽트 (Async IRQ)비동기 폴링 (Async Polling)
처리 주체CPU 자체 (AES-NI/CE)HW 가속기 + IRQHW 가속기 + CPU 폴링
완료 통지함수 반환 (ret=0)인터럽트 → softirq 콜백CPU가 completion 레지스터 폴링
CPU 점유연산 중 100% 점유제출·콜백만 — 중간에 자유폴링 루프로 코어 전용
지연 시간최저 (DMA 없음)중간 (IRQ 지연 5-50μs)최저 (즉시 감지, ~1μs)
처리량CPU 속도에 비례높음 (병렬 처리)최고 (IRQ 오버헤드 없음)
전력 효율중간높음 (유휴 시 절전)낮음 (코어 100% 점유)
적합 시나리오소형 패킷, SW 전용범용 HW 가속 (dm-crypt 등)DPDK/VPP 데이터 플레인
/proc/cryptoasync: noasync: yesasync: yes
대표 드라이버aesni-intel, ghash-clmulniqat, caam, otx2-cptDPDK cryptodev (UIO)
3종 완료 모델 CPU 타임라인 비교 시간 → 동기 호출 CPU 블로킹 (암호 연산 직접 수행) 완료 다음 작업 비동기 (IRQ) 제출 CPU 자유 (다른 패킷 처리) IRQ+콜백 다음 작업 HW 처리 HW 비동기 (폴링) 제출 busy-poll (완료 레지스터 감시) 감지 다음 패킷 즉시 처리 HW 처리 HW 시나리오별 최적 모델 소형 패킷·단순 암호 → 동기 범용 HW 가속 (dm-crypt, IPsec, AF_ALG) → 비동기 IRQ DPDK/VPP 데이터 플레인 → 비동기 폴링 적응형 NGFW → IRQ + 폴링 자동 전환
동기는 CPU를 독점하지만 최저 지연, IRQ는 CPU를 해방하지만 IRQ 오버헤드, 폴링은 최고 처리량이지만 코어 전용 필요

백로그(Backlog) 메커니즘

HW 가속기의 하드웨어 큐는 유한합니다. 큐가 가득 찬 상태에서 새 요청이 들어오면, CRYPTO_TFM_REQ_MAY_BACKLOG 플래그가 설정된 경우 커널은 요청을 소프트웨어 백로그 리스트에 추가하고 -EBUSY를 반환합니다. HW 큐에 빈 슬롯이 생기면 백로그에서 요청을 꺼내 자동으로 재제출합니다:

/* ━━━ 백로그 처리가 포함된 완전한 비동기 패턴 ━━━ */

/* 콜백 — 백로그를 올바르게 처리 */
static void my_crypto_complete(void *data, int err)
{
    struct my_ctx *ctx = data;

    if (err == -EINPROGRESS) {
        /* 백로그 경로 1차 콜백: 큐에 진입했다는 알림
         * → 아직 처리 완료가 아니므로 데이터에 접근하면 안 됨
         * → 필요하면 통계 카운터만 업데이트 */
        atomic_dec(&ctx->backlog_count);
        return;
    }

    /* 진짜 완료 — err=0이면 성공 */
    ctx->err = err;
    complete(&ctx->done);
}

/* 제출 측 */
int ret = crypto_skcipher_encrypt(req);

switch (ret) {
case 0:
    /* 즉시 완료 — 결과 사용 가능 */
    process_result(req);
    break;
case -EINPROGRESS:
    /* HW에서 처리 중 — 콜백으로 완료 통지 */
    break;
case -EBUSY:
    /* 큐 포화 → 백로그 진입 — 결국 콜백으로 완료 */
    atomic_inc(&ctx->backlog_count);
    break;
default:
    /* 에러 (콜백 없음) — 직접 에러 처리 */
    handle_error(ret);
    break;
}

crypto_engine — HW 드라이버의 비동기 큐 관리

crypto_engine은 커널이 HW 가속기 드라이버를 위해 제공하는 비동기 큐 관리 프레임워크입니다. 드라이버가 직접 큐잉·백로그·완료 통지를 구현하는 대신, crypto_engine에 위임하면 Crypto API 규약에 맞는 비동기 처리가 자동으로 이루어집니다:

crypto_engine 큐 관리 아키텍처 dm-crypt encrypt(req) IPsec (xfrm) encrypt(req) AF_ALG encrypt(req) crypto_engine crypto_queue (활성 큐) backlog 리스트 (대기열) kworker 스레드가 큐에서 1개씩 꺼내 do_one_request() 호출 HW 드라이버 do_one_request() → HW 레지스터 쓰기 → DMA 시작 return -EINPROGRESS HW 엔진 DMA 처리 완료 IRQ IRQ crypto_finalize_request() 호출자 콜백(err=0) → 결과 처리 engine이 자동 관리하는 것 큐잉, 백로그, 직렬화, 완료 통지, 반환값 계약
crypto_engine은 Crypto API 호출자와 HW 드라이버 사이에서 큐잉·백로그·직렬화·완료 통지를 자동 관리합니다
💡

crypto_engine을 사용하는 주요 드라이버: STM32 CRYP, Allwinner sun8i-ce, Rockchip crypto, ATMEL AES/SHA 등 중소형 SoC 가속기 드라이버가 crypto_engine을 사용합니다. CAAM, QAT, CPT 같은 고성능 드라이버는 자체 큐 관리를 구현합니다.

crypto_engine vs 자체 큐: crypto_engine은 단일 kworker 스레드로 요청을 직렬 처리하므로 구현이 단순하지만, 멀티큐 병렬 처리가 필요한 고성능 가속기에는 적합하지 않습니다. CAAM의 잡 링이나 CPT의 IQ처럼 HW 자체에 다중 큐가 있는 경우 crypto_queue를 직접 관리하는 것이 성능에 유리합니다. 자체 큐 구현의 상세는 crypto_queue를 직접 쓰는 드라이버 섹션을, 반환값 계약의 상세는 비동기 드라이버의 반환값 계약 섹션을 참고하세요.

비동기 패턴 실전 코드: crypto_wait_req()를 사용하는 전체 예제는 비동기 구현을 동기처럼 다루는 skcipher 패턴, ahash 비동기 예제는 ahash와 멀티 세그먼트 scatterlist 섹션을 참고하세요.

AEAD (Authenticated Encryption with Associated Data)

AEAD(Authenticated Encryption with Associated Data)암호화(기밀성)인증(무결성·진본성)을 단일 원자적 연산으로 결합하는 암호 프리미티브입니다. 전통적인 "encrypt-then-MAC" 조합과 달리 키 관리가 단순하고, 암호화·인증 순서 실수로 인한 취약점(Padding Oracle 등)이 구조적으로 불가능합니다. 커널에서 IPsec ESP, kTLS, WireGuard, dm-integrity+dm-crypt 등 거의 모든 현대 암호 프로토콜이 AEAD를 사용합니다.

AEAD 동작 원리

AEAD 알고리즘은 4개의 입력을 받아 암호문과 인증 태그(Authentication Tag)를 생성합니다:

AEAD 암호화/복호화 동작 원리 Encrypt (암호화) Key (키) IV / Nonce AAD (헤더) 인증만, 암호화 안 됨 Plaintext (평문) AEAD Encrypt (예: AES-GCM) Ciphertext (암호문) Auth Tag (인증 태그, 16B) Decrypt (복호화) — 태그 검증 실패 시 평문 미출력 Key IV / Nonce AAD Ciphertext Tag AEAD Decrypt + 태그 검증 Plaintext (성공 시) -EBADMSG (실패 시) AEAD의 보안 보장 ✓ 기밀성: 평문 노출 방지 ✓ 무결성: 변조 감지 ✓ 진본성: 발신자 인증 ✓ AAD 보호: 헤더 변조 감지 핵심 규칙 ① Nonce는 절대 재사용 금지 (AES-GCM 치명적) ② 태그 검증 실패 시 평문 절대 사용 금지 ③ AAD는 암호화되지 않음 (인증만 됨, 헤더 노출) ④ 태그 길이는 프로토콜 마다 고정 (보통 16B)
AEAD는 암호화와 인증을 단일 연산으로 수행하며, 복호화 시 태그 검증 실패(-EBADMSG)이면 평문을 출력하지 않습니다

커널에서 지원하는 주요 AEAD 알고리즘

알고리즘커널 이름Nonce/IV태그 크기주요 사용처HW 가속
AES-GCMgcm(aes)12B (96비트)16BIPsec ESP, kTLS, dm-cryptAES-NI+PCLMULQDQ, QAT, CAAM, CPT
AES-CCMccm(aes)7~13B (가변)4~16BBluetooth, IEEE 802.15.4AES-NI, ARM CE
ChaCha20-Poly1305rfc7539(chacha20,poly1305)12B16BWireGuard, TLS 1.3AVX2/AVX-512, ARM NEON
AES-GCM (RFC4106)rfc4106(gcm(aes))8B (ESP 시퀀스)16BIPsec ESP 전용AES-NI, QAT, CAAM, CPT
AES-GCM (RFC4543)rfc4543(gcm(aes))8B16BIPsec AH (GMAC)AES-NI
authenc(HMAC,CBC)authenc(hmac(sha256),cbc(aes))16B (CBC IV)32B (HMAC)레거시 IPsec, dm-cryptAES-NI + SHA-NI
Aegis-128aegis12816B16B고속 소프트웨어 AEADAES-NI (전용 최적화)

scatterlist 메모리 레이아웃

AEAD 요청에서 가장 실수가 많은 부분이 scatterlist 레이아웃입니다. encrypt와 decrypt에서 src/dst의 구성이 비대칭적이며, cryptlen의 의미도 다릅니다:

AEAD scatterlist 메모리 레이아웃 (Encrypt vs Decrypt) Encrypt req→src: AAD (assoclen) Plaintext (cryptlen) req→dst: AAD (복사됨) Ciphertext (cryptlen) Tag (authsize) cryptlen = 평문 길이 dst 크기 = assoclen + cryptlen + authsize Decrypt req→src: AAD (assoclen) Ciphertext Tag (authsize) req→dst: AAD (복사됨) Plaintext (복호화됨) cryptlen = 암호문 + 태그 길이 (= 원본 평문 길이 + authsize) 검증 실패 → -EBADMSG, dst 미기록 Encrypt vs Decrypt cryptlen 차이 (가장 흔한 실수) Encrypt: cryptlen = plaintext_len (태그 미포함) Decrypt: cryptlen = ciphertext_len + authsize (태그 포함!) ← 이것을 빠뜨리면 -EINVAL
Encrypt의 cryptlen은 평문 길이, Decrypt의 cryptlen은 암호문+태그 길이입니다. 이 비대칭성이 AEAD 사용에서 가장 흔한 실수 원인입니다

AEAD API 사용 패턴

/* ━━━ AEAD (AES-GCM) 암호화/복호화 전체 예제 ━━━ */

#include <crypto/aead.h>
#include <linux/scatterlist.h>

#define GCM_IV_SIZE   12   /* AES-GCM 표준 IV */
#define GCM_TAG_SIZE  16   /* 128-bit 인증 태그 */

/* ── 암호화 ── */
static int aead_encrypt_example(const u8 *key, unsigned int key_len,
                               const u8 *iv, const u8 *aad, unsigned int aad_len,
                               const u8 *plaintext, unsigned int pt_len,
                               u8 *output)  /* output 크기 = aad_len + pt_len + GCM_TAG_SIZE */
{
    struct crypto_aead *tfm;
    struct aead_request *req;
    struct scatterlist src_sg[2], dst_sg[3];
    u8 iv_local[GCM_IV_SIZE];
    int ret;
    DECLARE_CRYPTO_WAIT(wait);

    /* 1. tfm 할당 + 키·태그 크기 설정 */
    tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    ret = crypto_aead_setkey(tfm, key, key_len);
    if (ret) goto out_free_tfm;

    ret = crypto_aead_setauthsize(tfm, GCM_TAG_SIZE);
    if (ret) goto out_free_tfm;

    /* 2. request 할당 + 콜백 설정 */
    req = aead_request_alloc(tfm, GFP_KERNEL);
    if (!req) { ret = -ENOMEM; goto out_free_tfm; }

    aead_request_set_callback(req,
        CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
        crypto_req_done, &wait);

    /* 3. scatterlist 구성 */
    /* src: [AAD | Plaintext] */
    sg_init_table(src_sg, 2);
    sg_set_buf(&src_sg[0], aad, aad_len);
    sg_set_buf(&src_sg[1], plaintext, pt_len);

    /* dst: [AAD | Ciphertext | Tag] */
    sg_init_table(dst_sg, 3);
    sg_set_buf(&dst_sg[0], output, aad_len);               /* AAD 복사 영역 */
    sg_set_buf(&dst_sg[1], output + aad_len, pt_len);      /* 암호문 */
    sg_set_buf(&dst_sg[2], output + aad_len + pt_len, GCM_TAG_SIZE); /* 태그 */

    /* 4. 요청 파라미터 설정 */
    memcpy(iv_local, iv, GCM_IV_SIZE);
    aead_request_set_crypt(req, src_sg, dst_sg, pt_len, iv_local);
    /*                                         ^^^^^^ Encrypt: 평문 길이 */
    aead_request_set_ad(req, aad_len);

    /* 5. 암호화 수행 (비동기 지원) */
    ret = crypto_wait_req(crypto_aead_encrypt(req), &wait);

    aead_request_free(req);
out_free_tfm:
    crypto_free_aead(tfm);
    return ret;
}

/* ── 복호화 ── */
static int aead_decrypt_example(const u8 *key, unsigned int key_len,
                               const u8 *iv, const u8 *aad, unsigned int aad_len,
                               const u8 *ct_and_tag, unsigned int ct_len,
                               u8 *output)  /* output 크기 = aad_len + ct_len */
{
    /* ... tfm/req 할당 동일 ... */

    /* src: [AAD | Ciphertext | Tag] */
    aead_request_set_crypt(req, src_sg, dst_sg,
        ct_len + GCM_TAG_SIZE, iv_local);
    /* ^^^^^^^^^^^^^^^^^^^^^^^^ Decrypt: 암호문 + 태그 길이! */
    aead_request_set_ad(req, aad_len);

    ret = crypto_wait_req(crypto_aead_decrypt(req), &wait);
    /* ret == -EBADMSG: 인증 태그 불일치 → 데이터 위변조 감지 */
    /* ret == 0: 복호화 성공, output에 평문 기록됨 */
}

프로토콜별 AEAD 적용 구조

커널의 주요 네트워크·스토리지 프로토콜은 각각 다른 방식으로 AEAD를 적용합니다. 아래 다이어그램은 각 프로토콜이 AAD, 평문, 태그를 어떻게 배치하는지 보여줍니다:

프로토콜별 AEAD 패킷/레코드 구조 IPsec ESP (rfc4106(gcm(aes))) SPI+Seq (AAD) IV (8B) 암호화된 페이로드 + 패딩 + Next Hdr ICV (16B) ← 인증 태그 TLS 1.3 (gcm(aes) / rfc7539) TLS Header (AAD, 5B) 암호화된 레코드 + ContentType(1B) Tag (16B) ← Nonce = IV⊕Seq WireGuard (rfc7539(chacha20,poly1305)) Type+Idx+Ctr (AAD 없음) 암호화된 IP 패킷 Tag (16B) ← Nonce = Counter dm-crypt + dm-integrity (gcm(aes)) 섹터 번호 (AAD) 암호화된 섹터 데이터 (4096B) Tag (16B) ← 별도 무결성 영역 AEAD 알고리즘 선택 가이드 AES-GCM: HW 가속 보편적, 최고 처리량. 네트워크·스토리지 표준. Nonce 재사용 시 치명적. ChaCha20-Poly1305: HW AES 없는 환경에서 최적. 소프트웨어 성능 우수. Nonce misuse에 더 강건. authenc(HMAC,CBC): 레거시 호환용. 새 설계에서는 사용 지양 (Padding Oracle 위험, 성능 낮음).
각 프로토콜은 AAD·암호문·태그를 다르게 배치하며, 커널 AEAD API가 이 차이를 투명하게 처리합니다

AES-GCM 내부 동작

AES-GCM은 커널에서 가장 많이 사용되는 AEAD 알고리즘입니다. 내부적으로 AES-CTR(Counter Mode)로 암호화하고, GHASH(Galois Hash)로 인증 태그를 생성합니다. AES-NI와 PCLMULQDQ(캐리 없는 곱셈) 명령어를 사용하면 두 연산을 파이프라인으로 병렬 처리할 수 있습니다:

AES-GCM 내부 동작 흐름 Key (K) Nonce (12B) H = AES(K, 0¹²⁸) ← GHASH 서브키 J₀ = Nonce || 0³¹1 ← 초기 카운터 AES-CTR 암호화 파이프라인 AES(K, J₀+1) ⊕ P₁ → C₁ AES(K, J₀+2) ⊕ P₂ → C₂ AES(K, J₀+3) ⊕ P₃ → C₃ ... GHASH 인증 파이프라인 (병렬) GHASH(H, AAD || C₁ || C₂ || ...) || len(A) || len(C) ⊕ AES(K, J₀) → Tag HW 가속 매핑 AES-CTR → AES-NI (AESENC 명령) GHASH → PCLMULQDQ (캐리 없는 곱셈) 두 연산을 인터리빙하여 파이프라인 병렬화 AES-GCM 성능 (단일 코어 기준) AES-NI + PCLMULQDQ: ~5-10 GB/s | AVX-512 + VAES: ~15-25 GB/s | ARM CE: ~2-4 GB/s | HW (QAT/CAAM): CPU 무부하
AES-GCM은 AES-CTR(암호화)과 GHASH(인증)를 병렬로 수행하며, AES-NI+PCLMULQDQ 조합이 최적 가속 경로입니다

authenc(HMAC,CBC) vs 네이티브 AEAD 비교

커널의 authenc 템플릿은 기존 블록 암호(CBC)와 MAC(HMAC)을 조합하여 AEAD 인터페이스를 제공하는 호환 레이어입니다. 네이티브 AEAD(GCM, ChaCha20-Poly1305)와의 핵심 차이를 이해하면 적절한 알고리즘을 선택할 수 있습니다:

항목authenc(hmac(sha256),cbc(aes))gcm(aes)rfc7539(chacha20,poly1305)
내부 구조CBC 암호화 → HMAC 생성 (2-pass)CTR + GHASH 병렬 (1-pass)ChaCha20 + Poly1305 (1-pass)
키 크기48B (AES-256 32B + HMAC 16B)16/32B (AES-128/256)32B
Nonce 크기16B (CBC IV)12B12B
태그 크기32B (SHA-256 출력)16B16B
패딩필요 (PKCS#7)불필요불필요
Padding Oracle취약 가능 (구현 주의)구조적 불가구조적 불가
HW 병렬화2-pass로 비효율CTR+GHASH 파이프라인SW 최적화 우수
성능 (AES-NI)~2-3 GB/s~5-10 GB/s~3-5 GB/s
사용처레거시 IPsec, 구형 TLSIPsec, kTLS, dm-cryptWireGuard, TLS 1.3
권장호환용만, 신규 설계 지양HW AES 있으면 최적HW AES 없으면 최적
💡

Nonce 재사용의 위험: AES-GCM에서 동일 키로 동일 Nonce를 두 번 사용하면 XOR 차분으로 두 평문의 관계가 노출되고, GHASH 서브키(H)가 복구되어 인증이 완전히 무력화됩니다. IPsec ESP는 시퀀스 번호를 Nonce로 사용하여 자동 방지하고, kTLS도 레코드 시퀀스를 사용합니다. 직접 구현 시 카운터 기반 Nonce 생성이 필수입니다.

참고: AEAD 상태 머신의 encrypt/decrypt 경로 전이는 AEAD 상태 머신, authenc 내부 구현 분석은 authenc 내부 구현 분석, scatterlist 구성 상세 예제는 AEAD scatterlist 메모리 레이아웃, 레코드 처리 코드 패턴은 AEAD 레코드 처리 패턴 섹션을 참고하세요.

커널 난수 생성기

암호화 시스템의 안전성은 키·IV·Nonce의 예측 불가능성에 달려 있으며, 이를 제공하는 것이 난수 생성기(Random Number Generator)입니다. 리눅스 커널은 다계층 난수 아키텍처를 사용합니다: 하드웨어 엔트로피 소스(TRNG/hwrng)가 물리적 무작위성을 수집하고, 커널 엔트로피 풀이 이를 축적한 뒤, ChaCha20 기반 CRNG(Cryptographically Secure PRNG)가 고속으로 난수를 생성합니다. Crypto API의 crypto_rng 인터페이스는 NIST SP 800-90A DRBG를 FIPS 호환 방식으로 제공합니다.

커널 난수 아키텍처

리눅스 커널 난수 생성 아키텍처 하드웨어 엔트로피 소스 (물리적 무작위성) CPU RDRAND Intel/AMD 내장 hwrng 디바이스 CAAM RNG, TPM 등 인터럽트 지터 타이밍 노이즈 디스크/입력 I/O 타이밍 RDSEED / Jitter 시드 전용 엔트로피 커널 엔트로피 풀 (input_pool) BLAKE2s 기반 해시 → 256비트 시드 추출 → crng_reseed()로 CRNG에 주입 시드 (5분마다 또는 256B 출력마다) ChaCha20 CRNG (per-CPU) CPU별 독립 상태 → lock-free 고속 생성 → get_random_bytes() / /dev/urandom 커널 내부 get_random_bytes(), 키 생성 사용자 공간 /dev/urandom, getrandom(2) Crypto API crypto_rng (DRBG) NIST DRBG (SP 800-90A) drbg_nopr_hmac_sha256 / drbg_pr_ctr_aes256 FIPS 인증 환경 전용, 자체 reseed 관리 계층 요약 물리적 엔트로피 → 축적·혼합 → CRNG 확장 → 소비자 일반 용도: get_random_bytes() | FIPS: crypto_rng("drbg_...") | HW 직접: hwrng
물리적 엔트로피가 입력 풀에 축적되고, ChaCha20 CRNG가 per-CPU 상태로 고속 난수를 생성합니다

난수 API 비교 — 언제 무엇을 쓸 것인가

API헤더용도컨텍스트FIPS 호환성능
get_random_bytes()<linux/random.h>범용 암호학적 난수 (키, IV, Nonce)어디서든아니오매우 빠름 (per-CPU)
get_random_u32() / u64()<linux/random.h>정수 난수 (해시 시드, 주소 무작위화)어디서든아니오매우 빠름 (배치)
get_random_u32_below(n)<linux/random.h>범위 제한 난수 (0~n-1)어디서든아니오매우 빠름
crypto_rng (DRBG)<crypto/rng.h>FIPS DRBG, 테스트 벡터 재현process ctx보통
getrandom(2)syscall유저스페이스 암호 난수유저아니오빠름 (vDSO 가능)
/dev/urandom-유저스페이스 레거시 인터페이스유저아니오빠름
/dev/random-/dev/urandom과 동일 (6.x+)유저아니오빠름
/dev/hwrng-HW TRNG 직접 읽기유저소스에 따라느림 (HW 속도)
⚠️

일반 코드에서는 항상 get_random_bytes()를 사용하세요. crypto_rng는 FIPS 경계 안에서 특정 DRBG를 명시적으로 선택해야 하거나 테스트 벡터를 재현해야 할 때만 필요합니다. 대부분의 커널 서브시스템(dm-crypt, IPsec, WireGuard 등)은 get_random_bytes()로 키와 Nonce를 생성합니다.

커널 공간 난수 API 사용 예제

#include <linux/random.h>

/* ━━━ 범용 암호학적 난수 (가장 일반적) ━━━ */

u8 aes_key[32];
get_random_bytes(aes_key, sizeof(aes_key));  /* 256-bit AES 키 */

u8 iv[16];
get_random_bytes(iv, sizeof(iv));             /* CBC IV */

u8 nonce[12];
get_random_bytes(nonce, sizeof(nonce));       /* GCM Nonce */

/* ━━━ 정수 난수 ━━━ */

u32 hash_seed = get_random_u32();             /* 해시 테이블 시드 */
u64 cookie = get_random_u64();                /* TCP SYN 쿠키 */
u32 idx = get_random_u32_below(100);          /* 0~99 범위 */

/* ━━━ 초기 부팅 시 주의사항 ━━━ */
/* 부팅 초기에는 엔트로피 풀이 충분히 채워지지 않을 수 있음.
 * get_random_bytes()는 CRNG가 초기화된 후에만 완전한 품질을 보장.
 * wait_for_random_bytes()로 초기화 완료를 기다릴 수 있음 */
ret = wait_for_random_bytes();  /* CRNG 준비될 때까지 대기 */
if (ret)
    pr_warn("CRNG not ready, random may be weak\n");

Crypto API DRBG — FIPS 호환 난수 생성

Crypto API의 crypto_rng 인터페이스는 NIST SP 800-90A 규격의 DRBG(Deterministic Random Bit Generator)를 제공합니다. 일반 용도에서는 get_random_bytes()가 충분하지만, FIPS 인증 환경이나 테스트 벡터 재현이 필요한 경우 DRBG를 직접 사용합니다:

DRBG 알고리즘커널 이름내부 암호예측 저항용도
HMAC-DRBG SHA-256drbg_nopr_hmac_sha256HMAC-SHA256없음 (nopr)FIPS 기본, 테스트 벡터
HMAC-DRBG SHA-512drbg_nopr_hmac_sha512HMAC-SHA512없음고보안 FIPS
CTR-DRBG AES-256drbg_nopr_ctr_aes256AES-256-CTR없음AES-NI 가속 FIPS
HMAC-DRBG SHA-256 (PR)drbg_pr_hmac_sha256HMAC-SHA256있음 (pr)최고 보안 (매번 reseed)
CTR-DRBG AES-256 (PR)drbg_pr_ctr_aes256AES-256-CTR있음FIPS + AES 가속
#include <crypto/rng.h>

/* ━━━ Crypto API DRBG 사용 예제 ━━━ */
static int generate_fips_random(u8 *out, unsigned int len)
{
    struct crypto_rng *drbg;
    u8 seed[64];
    int ret;

    /* 1. DRBG 인스턴스 할당 */
    drbg = crypto_alloc_rng("drbg_nopr_hmac_sha256", 0, 0);
    if (IS_ERR(drbg))
        return PTR_ERR(drbg);

    /* 2. 엔트로피 시드 준비 → DRBG에 주입 */
    get_random_bytes(seed, crypto_rng_seedsize(drbg));
    ret = crypto_rng_reset(drbg, seed, crypto_rng_seedsize(drbg));
    if (ret)
        goto out;

    /* 3. DRBG에서 난수 생성 */
    ret = crypto_rng_get_bytes(drbg, out, len);
    /* ret == 0: 성공, -EINVAL: 파라미터 오류 */

out:
    memzero_explicit(seed, sizeof(seed));
    crypto_free_rng(drbg);
    return ret;
}

엔트로피 소스와 HW RNG

커널이 수집하는 엔트로피 소스와 각각의 특성입니다. 다중 소스를 혼합(mixing)하여 단일 소스의 결함이 전체 시스템을 위협하지 않도록 합니다:

소스메커니즘엔트로피 품질속도가용성
RDRAND/RDSEEDCPU 내장 디지털 노이즈높음 (직접 HW)~800 MB/sIntel Ivy Bridge+ / AMD Zen+
hwrng (TRNG)SoC 내장 열잡음/진동높음1~100 MB/sCAAM RNG, STM32 RNG, TPM
인터럽트 지터IRQ 도착 타이밍 차이중간느림항상 (부팅 초기 핵심)
디스크 I/Oseek/완료 타이밍중간느림블록 디바이스 있을 때
입력 디바이스키보드/마우스 타이밍낮음~중간간헐적데스크탑 환경
Jitter EntropyCPU 실행 시간 변동중간~1 MB/s항상 (v5.18+ 내장)
부트로더 시드EFI 변수 / DT높음 (이전 세션)1회EFI / DT 지원 시
엔트로피 혼합 원리 — 단일 소스 결함 방어 RDRAND ✓ hwrng ✓ IRQ jitter ✗ (결함 가정) Jitter ✓ BLAKE2s 혼합 (XOR + 해시) 안전한 시드 출력 ✓ 혼합의 보안 특성 • N개 소스 중 1개라도 정상이면 출력은 암호학적으로 안전 • 결함 소스는 안전성을 낮추지 않음 (해시가 정보를 축약) • RDRAND을 유일한 소스로 신뢰하지 않음 (커널 정책)
커널은 다수의 엔트로피 소스를 BLAKE2s로 혼합하여, 개별 소스의 결함이 전체 난수 품질에 영향을 주지 않도록 합니다

ChaCha20 CRNG 내부 구조

리눅스 6.x의 핵심 CRNG는 ChaCha20 스트림 암호 기반입니다. CPU별(per-CPU) 독립 상태를 유지하여 lock-free 고속 생성이 가능합니다:

/* drivers/char/random.c — ChaCha20 CRNG 핵심 구조 (간소화) */

struct crng_state {
    u32 state[16];     /* ChaCha20 상태 (512비트) */
    unsigned long birth; /* 마지막 reseed 시각 */
};

/* per-CPU CRNG — 각 CPU가 독립 상태를 보유 */
static DEFINE_PER_CPU(struct crng_state, crngs);

/* get_random_bytes() 내부 동작 (간소화) */
void get_random_bytes(void *buf, size_t len)
{
    struct crng_state *crng = this_cpu_ptr(&crngs);

    /* 1. reseed 필요 여부 확인 (5분 또는 256B 출력마다) */
    if (crng_needs_reseed(crng))
        crng_reseed(crng);  /* 엔트로피 풀에서 256비트 시드 추출 → 키 갱신 */

    /* 2. ChaCha20 블록 생성 → buf에 복사 */
    chacha20_block(crng->state, buf);

    /* 3. backtrack 방지: 키 부분을 출력에서 즉시 갱신 */
    crng_fast_key_erasure(crng->state);
    /* → 과거 출력에서 키를 역추적하는 것이 불가능 */
}
ℹ️

fast key erasure: CRNG은 난수 블록을 생성할 때마다 키의 일부를 출력 데이터로 갱신합니다. 이를 통해 공격자가 현재 CRNG 상태를 탈취하더라도 과거에 생성된 난수를 역추적(backtrack)할 수 없습니다. 이것은 커널 CRNG의 핵심 보안 특성입니다.

/dev/random vs /dev/urandom (6.x): 커널 6.x부터 /dev/random/dev/urandom동일한 CRNG에서 출력합니다. 과거의 "엔트로피 소진 시 블로킹" 동작은 제거되었습니다. getrandom(2) 시스템 콜은 CRNG 초기화 전에만 블로킹하며, 초기화 후에는 항상 즉시 반환합니다.

참고: hwrng 서브시스템 아키텍처, QRNG 드라이버 개발, 엔트로피 풀 BLAKE2s 메커니즘, ChaCha20-CRNG 상태 갱신 상세는 Linux 하드웨어 난수 생성기 (hwrng & QRNG) 페이지에서 심층적으로 다룹니다. Crypto API의 DRBG 드라이버 구현은 RNG 드라이버 구현 섹션, DRBG를 직접 다루는 코드 패턴은 crypto_rng로 DRBG를 직접 다루는 패턴 섹션을 참고하세요.

주요 알고리즘 카탈로그

카테고리알고리즘커널 이름용도
블록 암호AES-128/256aes디스크 암호화, IPsec
스트림 암호ChaCha20chacha20WireGuard, TLS
해시SHA-256sha256무결성 검증
해시BLAKE2bblake2b-256고속 해싱
MACHMAC-SHA256hmac(sha256)메시지 인증
AEADAES-GCMgcm(aes)TLS, IPsec
AEADChaCha20-Poly1305rfc7539(chacha20,poly1305)WireGuard
KDFHKDFhkdf(hmac(sha256))키 유도
압축LZ4, ZSTDlz4, zstdzswap, 파일시스템(Filesystem)

사용자 공간(User Space) 인터페이스 (AF_ALG)

/* 사용자 공간에서 커널 Crypto Framework (Crypto API) 사용 */
int sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type   = "hash",
    .salg_name   = "sha256",
};
bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));

int opfd = accept(sockfd, NULL, NULL);
write(opfd, data, data_len);
read(opfd, digest, 32);  /* SHA-256 결과 */

H/W 가속 상세

# 사용 가능한 암호 알고리즘 확인 (H/W 가속 포함)
cat /proc/crypto | head -40
# name         : __cbc(aes)
# driver       : __cbc-aes-aesni  ← AES-NI 하드웨어 가속
# module       : aesni_intel
# priority     : 400             ← 높은 우선순위 = 자동 선택
# type         : skcipher

# AES-NI 지원 확인
grep aes /proc/cpuinfo | head -1
# flags : ... aes ...
ℹ️

커널은 동일 알고리즘의 여러 구현 중 priority가 가장 높은 것을 자동 선택합니다. H/W 가속기 드라이버가 로드되면 자동으로 소프트웨어 구현보다 우선 사용됩니다.

AES-NI

AES-NI(Advanced Encryption Standard New Instructions)는 Intel이 2010년(Westmere)에 도입하고 AMD가 2011년(Bulldozer)부터 지원하는 AES 전용 하드웨어 명령어 세트입니다. 소프트웨어 AES 대비 3~10배 이상 빠른 처리량(Throughput)을 제공하며, 일반적인 테이블 기반 구현 대비 타이밍 기반 부채널 위험을 줄이는 데 유리합니다.

AES-NI 명령어 세트

AES-NI는 6개의 핵심 명령어로 구성됩니다. 모든 명령어는 128-bit XMM 레지스터(Register)에서 동작합니다:

명령어동작설명
AESENC1라운드 암호화ShiftRows → SubBytes → MixColumns → AddRoundKey
AESENCLAST마지막 라운드 암호화ShiftRows → SubBytes → AddRoundKey (MixColumns 생략)
AESDEC1라운드 복호화InvShiftRows → InvSubBytes → InvMixColumns → AddRoundKey
AESDECLAST마지막 라운드 복호화InvShiftRows → InvSubBytes → AddRoundKey
AESKEYGENASSIST키 확장 보조라운드 키 생성에 필요한 SubWord/RotWord 수행
AESIMC역 MixColumns복호화용 라운드 키 변환 (Equivalent Inverse Cipher)
ℹ️

AES 라운드 수: AES-128은 10라운드, AES-192는 12라운드, AES-256은 14라운드입니다. 각 라운드마다 AESENC 1개 명령어가 전체 라운드 변환을 수행합니다. 소프트웨어 구현에서는 S-Box 테이블 룩업, 행 시프트, 열 혼합, 키 합성을 별도로 수행하지만 AES-NI는 이를 단일 명령어로 처리합니다.

AES-NI 키 확장 (Key Expansion)

AES 키 확장은 원본 키(128/192/256비트)로부터 각 라운드에 사용할 라운드 키를 생성합니다. 커널의 aesni-intel_glue.c는 키 설정 시 모든 라운드 키를 미리 확장하여 crypto_aes_ctx에 저장합니다:

; AES-128 키 확장 (arch/x86/crypto/aesni-intel_asm.S 참고)
; 입력: %xmm0 = 원본 128-bit 키
; 출력: key_schedule[0..10] = 11개 라운드 키

_aesni_key_expansion_128:
    movaps  %xmm0, (%rdi)          ; key_schedule[0] = 원본 키

    ; 라운드 1: RCON = 0x01
    aeskeygenassist $0x01, %xmm0, %xmm1
    call    _key_expansion_128
    movaps  %xmm0, 0x10(%rdi)    ; key_schedule[1]

    ; 라운드 2: RCON = 0x02
    aeskeygenassist $0x02, %xmm0, %xmm1
    call    _key_expansion_128
    movaps  %xmm0, 0x20(%rdi)    ; key_schedule[2]
    ; ... 라운드 3~10까지 반복 (RCON: 0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36)

; 키 확장 보조 루틴
_key_expansion_128:
    pshufd  $0xff, %xmm1, %xmm1  ; AESKEYGENASSIST 결과를 broadcast
    shufps  $0x10, %xmm0, %xmm4  ; temp = [0,0,key[0],key[1]]
    pxor    %xmm4, %xmm0          ; key ^= temp
    shufps  $0x8c, %xmm0, %xmm4
    pxor    %xmm4, %xmm0          ; key ^= temp (cascade)
    pxor    %xmm1, %xmm0          ; key ^= RCON 결과
    ret
/* 커널 glue 코드: 키 설정 (arch/x86/crypto/aesni-intel_glue.c) */
static int aesni_set_key(struct crypto_aes_ctx *ctx,
                        const u8 *in_key, unsigned int key_len)
{
    if (!crypto_simd_usable())
        return aes_expandkey(ctx, in_key, key_len);  /* SW fallback */

    kernel_fpu_begin();
    aesni_set_key_common(ctx, in_key, key_len);  /* AES-NI 키 확장 */
    kernel_fpu_end();

    return 0;
}

/* struct crypto_aes_ctx — 확장된 키 스케줄 저장 */
struct crypto_aes_ctx {
    u32 key_enc[AES_MAX_KEYLENGTH_U32];  /* 암호화용 라운드 키 (60 u32) */
    u32 key_dec[AES_MAX_KEYLENGTH_U32];  /* 복호화용 라운드 키 (Inv) */
    u32 key_length;                      /* 16, 24, 또는 32 */
};

운용 모드별 AES-NI 구현

커널의 aesni_intel 모듈은 다양한 운용 모드를 AES-NI로 가속합니다. 각 모드의 병렬화 특성에 따라 성능이 크게 달라집니다:

모드커널 드라이버 이름병렬 처리특성
ECBecb-aes-aesni완전 병렬각 블록이 독립적이라 파이프라인(Pipeline) 최대 활용
CBC 암호화cbc-aes-aesni직렬 (체인)이전 블록 암호문이 다음 블록 입력에 필요
CBC 복호화cbc-aes-aesni완전 병렬복호화는 모든 암호문 블록을 이미 알고 있으므로 병렬 가능
CTRctr-aes-aesni완전 병렬카운터 값이 독립적, IPsec/TLS에서 주력으로 사용
XTSxts-aes-aesni완전 병렬디스크 암호화(dm-crypt, LUKS) 표준 모드
GCMgcm-aes-aesniAES 병렬 + GHASHAEAD, PCLMULQDQ로 GHASH 가속 포함
; AES-CBC 암호화 — 직렬 체인 (arch/x86/crypto/aesni-intel_asm.S 참고)
; 각 블록: C[i] = AES_ENC(P[i] XOR C[i-1])
_aesni_enc_cbc:
    movups  (%r8), %xmm2           ; IV 로드
.Lcbc_enc_loop:
    movups  (%rsi), %xmm3          ; 평문 블록 로드
    pxor    %xmm2, %xmm3           ; P[i] XOR C[i-1] (CBC 체이닝)

    ; 10라운드 AES-128 암호화
    pxor    0x00(%rdi), %xmm3     ; AddRoundKey (라운드 0)
    aesenc  0x10(%rdi), %xmm3     ; 라운드 1
    aesenc  0x20(%rdi), %xmm3     ; 라운드 2
    ; ... 라운드 3~9 ...
    aesenclast 0xa0(%rdi), %xmm3  ; 라운드 10 (마지막)

    movups  %xmm3, (%rdx)          ; 암호문 저장
    movaps  %xmm3, %xmm2           ; C[i] → 다음 블록의 IV
    add     $16, %rsi
    add     $16, %rdx
    dec     %ecx
    jnz     .Lcbc_enc_loop

; AES-CTR 암호화 — 병렬 처리 (4블록 동시)
; C[i] = P[i] XOR AES_ENC(CTR+i)
_aesni_enc_ctr:
    ; 4개 카운터를 병렬로 준비
    movaps  %xmm0, %xmm1           ; CTR+0
    movaps  %xmm0, %xmm2           ; CTR+1 (inc)
    movaps  %xmm0, %xmm3           ; CTR+2 (inc)
    movaps  %xmm0, %xmm4           ; CTR+3 (inc)
    ; ... 각 카운터 증가 ...

    ; 4블록 동시 AES 라운드 (파이프라인 활용)
    pxor    (%rdi), %xmm1
    pxor    (%rdi), %xmm2
    pxor    (%rdi), %xmm3
    pxor    (%rdi), %xmm4
    aesenc  0x10(%rdi), %xmm1
    aesenc  0x10(%rdi), %xmm2
    aesenc  0x10(%rdi), %xmm3
    aesenc  0x10(%rdi), %xmm4
    ; ... 나머지 라운드 인터리빙 ...
💡

CTR/ECB 병렬화 핵심: 최신 CPU의 AES-NI 파이프라인은 4사이클 레이턴시, 1사이클 스루풋입니다. AESENC 명령어는 이전 명령어 완료를 기다리지 않고 파이프라인에 투입되므로, 독립적인 블록 4~8개를 인터리빙하면 이론적 최대 스루풋에 도달합니다.

AES-GCM과 PCLMULQDQ

AES-GCM(Galois/Counter Mode)은 TLS 1.3, IPsec에서 가장 널리 사용되는 AEAD 모드입니다. GCM은 CTR 모드 암호화와 GHASH 인증을 결합합니다. 커널은 AES-NI와 PCLMULQDQ(Carry-less Multiplication) 명령어를 함께 사용하여 두 연산을 모두 하드웨어로 가속합니다:

AES-GCM 동작 구조 Counter (J₀+i) AES-NI ENC AESENC × 10 rounds XOR ⊕ Plaintext Ciphertext AAD 추가 인증 데이터 XOR ⊕ H_i GHASH (GF(2¹²⁸)) PCLMULQDQ 가속 H_{i+1} GHASH Final ⊕ AES_ENC(J₀) Auth Tag (128-bit) ← 기밀성 ← 무결성/인증
AES-GCM: CTR 모드(기밀성) + GHASH(인증)를 병렬 수행
/* GHASH에서 GF(2^128) 곱셈 — PCLMULQDQ 하드웨어 가속 */
/* PCLMULQDQ: Carry-less multiplication (XOR 기반 다항식 곱셈) */
/* 소프트웨어로 구현하면 수십 사이클 걸리는 연산을 단일 명령어로 수행 */

/* arch/x86/crypto/ghash-clmulni-intel_asm.S 에서: */
; GHASH 블록 처리:
;   xmm0 = 현재 해시 값 (H_i)
;   xmm1 = 해시 키 (H = AES_ENC(0))
;   XOR → CLMUL → reduction → 새 해시 값
pxor    %xmm2, %xmm0              ; H_i XOR C_i (입력 블록과 XOR)

; Karatsuba 분해를 사용한 128×128 bit carry-less 곱셈
movdqa  %xmm0, %xmm3
pclmulqdq $0x00, %xmm1, %xmm3    ; a0 × b0 (하위 64-bit 곱)
movdqa  %xmm0, %xmm4
pclmulqdq $0x11, %xmm1, %xmm4    ; a1 × b1 (상위 64-bit 곱)
movdqa  %xmm0, %xmm5
pclmulqdq $0x10, %xmm1, %xmm5    ; a0 × b1 (교차 곱)
pclmulqdq $0x01, %xmm1, %xmm0    ; a1 × b0 (교차 곱)
pxor    %xmm5, %xmm0              ; 교차 곱 합산
; → GF(2^128) 환원(reduction): x^128 + x^7 + x^2 + x + 1
ℹ️

커널 GCM 구현 구조: generic-gcm-aesni 드라이버(priority 400)는 AES-NI + PCLMULQDQ를 결합한 최적화 구현입니다. CTR 암호화와 GHASH 인증을 인터리빙하여 AES 파이프라인 대기 시간(Latency) 동안 GHASH를 수행합니다. 이 기법으로 GCM은 CTR 단독 대비 거의 추가 비용 없이 인증을 제공합니다.

aesni_intel 모듈 아키텍처

커널의 AES-NI 지원은 arch/x86/crypto/ 디렉터리에 위치하며, glue 코드와 어셈블리 구현으로 분리됩니다:

aesni_intel 모듈 아키텍처 arch/x86/crypto/ C Glue 계층 aesni-intel_glue.c ghash-clmulni-intel_glue.c 역할: 알고리즘 등록, setkey, FPU 관리 어셈블리 최적화 계층 aesni-intel_asm.S (ECB/CBC/CTR) aes_ctrby8_avx-x86_64.S (CTR by8) aesni-intel_avx-x86_64.S (GCM) ghash-clmulni-intel_asm.S, aes-gcm-aesni-x86_64.S
/* aesni-intel_glue.c — 주요 알고리즘 등록 구조 */

/* 1. 기본 블록 암호 (AES 단일 블록, 128-bit) */
static struct crypto_alg aesni_cipher_alg = {
    .cra_name           = "aes",
    .cra_driver_name    = "aes-aesni",
    .cra_priority       = 300,
    .cra_flags          = CRYPTO_ALG_TYPE_CIPHER,
    .cra_blocksize      = AES_BLOCK_SIZE,       /* 16 */
    .cra_u.cipher = {
        .cia_min_keysize = AES_MIN_KEY_SIZE,    /* 16 */
        .cia_max_keysize = AES_MAX_KEY_SIZE,    /* 32 */
        .cia_setkey      = aes_set_key,
        .cia_encrypt     = aesni_encrypt,
        .cia_decrypt     = aesni_decrypt,
    },
};

/* 2. skcipher 알고리즘들 (CBC, CTR, XTS, ECB) */
static struct skcipher_alg aesni_skciphers[] = {
    {   /* ECB — 병렬 처리, priority 400 */
        .base.cra_name          = "__ecb(aes)",
        .base.cra_driver_name   = "__ecb-aes-aesni",
        .base.cra_priority      = 400,
        .base.cra_flags         = CRYPTO_ALG_INTERNAL,
        .setkey                 = aesni_skcipher_setkey,
        .encrypt                = ecb_encrypt,
        .decrypt                = ecb_decrypt,
    },
    {   /* CBC */
        .base.cra_name          = "__cbc(aes)",
        .base.cra_driver_name   = "__cbc-aes-aesni",
        .base.cra_priority      = 400,
        .base.cra_flags         = CRYPTO_ALG_INTERNAL,
        .setkey                 = aesni_skcipher_setkey,
        .encrypt                = cbc_encrypt,
        .decrypt                = cbc_decrypt,
    },
    {   /* CTR */
        .base.cra_name          = "__ctr(aes)",
        .base.cra_driver_name   = "__ctr-aes-aesni",
        .base.cra_priority      = 400,
        .base.cra_flags         = CRYPTO_ALG_INTERNAL,
        .setkey                 = aesni_skcipher_setkey,
        .encrypt                = ctr_crypt,
        .decrypt                = ctr_crypt,    /* CTR 모드: enc == dec */
    },
    {   /* XTS — 디스크 암호화 (dm-crypt) */
        .base.cra_name          = "__xts(aes)",
        .base.cra_driver_name   = "__xts-aes-aesni",
        .base.cra_priority      = 401,
        .setkey                 = xts_aesni_setkey,
        .encrypt                = xts_encrypt,
        .decrypt                = xts_decrypt,
    },
};

/* 3. AEAD (GCM) */
static struct aead_alg aesni_aeads[] = {
    {
        .base.cra_name          = "__gcm(aes)",
        .base.cra_driver_name   = "__generic-gcm-aesni",
        .base.cra_priority      = 400,
        .base.cra_flags         = CRYPTO_ALG_INTERNAL,
        .setkey                 = gcm_setkey,
        .setauthsize            = gcm_setauthsize,
        .encrypt                = gcm_encrypt,
        .decrypt                = gcm_decrypt,
        .ivsize                 = GCM_AES_IV_SIZE,    /* 12 */
        .maxauthsize            = 16,
    },
};
ℹ️

INTERNAL 플래그와 SIMD 래퍼: __ecb-aes-aesni__ 접두사 알고리즘은 CRYPTO_ALG_INTERNAL 플래그를 가지며 직접 사용할 수 없습니다. simd_register_skciphers_compat()가 이를 감싸서 ecb-aes-aesni(접두사 없음)를 외부에 공개합니다. SIMD 래퍼는 process context에서 직접 SIMD 실행, softirq/hardirq에서는 cryptd kthread로 위임하여 컨텍스트 안전성을 보장합니다.

aesni_intel 모듈 초기화 흐름

/* aesni-intel_glue.c — 모듈 초기화 */
static int __init aesni_init(void)
{
    int err;

    /* 1. CPU가 AES-NI를 지원하는지 확인 */
    if (!boot_cpu_has(X86_FEATURE_AES))
        return -ENODEV;       /* AES-NI 미지원 → 모듈 로드 실패 → generic 사용 */

    /* 2. PCLMULQDQ 지원 여부 확인 (GCM GHASH에 필요) */
    if (!boot_cpu_has(X86_FEATURE_PCLMULQDQ))
        pr_info("PCLMULQDQ not available, GCM acceleration disabled\\n");

    /* 3. 기본 AES 블록 암호 등록 */
    err = crypto_register_alg(&aesni_cipher_alg);
    if (err)
        return err;

    /* 4. skcipher 알고리즘 등록 (INTERNAL 버전) */
    err = crypto_register_skciphers(aesni_skciphers,
                                    ARRAY_SIZE(aesni_skciphers));

    /* 5. SIMD 래퍼 등록 (외부 공개 버전)
     * __ecb-aes-aesni → ecb-aes-aesni
     * __cbc-aes-aesni → cbc-aes-aesni
     * ... */
    err = simd_register_skciphers_compat(aesni_skciphers,
                                         ARRAY_SIZE(aesni_skciphers),
                                         aesni_simd_skciphers);

    /* 6. AEAD (GCM) 등록 */
    if (boot_cpu_has(X86_FEATURE_PCLMULQDQ)) {
        err = crypto_register_aeads(aesni_aeads,
                                    ARRAY_SIZE(aesni_aeads));
        err = simd_register_aeads_compat(aesni_aeads,
                                         ARRAY_SIZE(aesni_aeads),
                                         aesni_simd_aeads);
    }

    return 0;
}

VAES: AVX-512 벡터 AES

VAES(Vector AES)는 Intel Ice Lake(2019)부터 도입된 확장으로, AES-NI를 AVX-512 레지스터(512-bit)에서 동작하게 합니다. 128-bit XMM 대신 512-bit ZMM 레지스터를 사용하여 한 명령어로 4개 AES 블록을 동시에 처리합니다:

세대레지스터블록/명령어인터리빙 시 최대
AES-NI (SSE)XMM (128-bit)1블록~4블록 (파이프라인)
VAES + AVX2YMM (256-bit)2블록~8블록
VAES + AVX-512ZMM (512-bit)4블록~16블록
; VAES 예시: 512-bit ZMM 레지스터로 4블록 동시 AES 라운드
; 기존 AES-NI: aesenc %xmm_key, %xmm_data  → 1블록 (128-bit)
; VAES:       vaesenc %zmm_key, %zmm_data, %zmm_out → 4블록 (512-bit)

vaesenc %zmm1, %zmm0, %zmm0     ; 4블록 × 라운드 1
vaesenc %zmm2, %zmm0, %zmm0     ; 4블록 × 라운드 2
vaesenc %zmm3, %zmm0, %zmm0     ; 4블록 × 라운드 3
; ... 라운드 4~9 ...
vaesenclast %zmm11, %zmm0, %zmm0 ; 4블록 × 라운드 10 (마지막)

; 2개 ZMM을 인터리빙하면 8블록(1024-bit) 동시 처리:
vaesenc %zmm1, %zmm10, %zmm10   ; 블록 0~3
vaesenc %zmm1, %zmm11, %zmm11   ; 블록 4~7 (파이프라인 활용)
# VAES 지원 확인
grep vaes /proc/cpuinfo | head -1
# flags : ... vaes avx512f avx512bw ...

# 커널에서 VAES 가속 드라이버 확인
grep -E "aes.*(vaes|avx512)" /proc/crypto
# driver : xts-aes-vaes-avx512
# driver : gcm-aes-vaes-avx512
ℹ️

커널 VAES 지원: Linux 6.4+ 계열 x86에서는 arch/x86/crypto/aes-xts-avx-x86_64.S 등의 VAES+AVX-512 최적화 경로가 제공됩니다. 실제 선택 여부는 CPU 기능, 커널 설정, 알고리즘 등록 우선순위에 따라 달라질 수 있습니다.

AES-NI를 활용하는 주요 커널 서브시스템

서브시스템사용 모드설정/모듈설명
dm-crypt / LUKS XTS(AES-256) CONFIG_DM_CRYPT 디스크 전체 암호화. cryptsetup으로 설정, 기본 aes-xts-plain64
IPsec (XFRM) GCM(AES-128/256) CONFIG_XFRM VPN 터널(Tunnel) 암호화. ESP 프로토콜에서 AES-GCM이 기본 선택
kTLS GCM(AES-128/256) CONFIG_TLS 커널 내 TLS 오프로드. setsockopt(SOL_TLS)로 활성화
WireGuard CONFIG_WIREGUARD ChaCha20-Poly1305 사용 (AES-NI 미사용), 대신 SSSE3/AVX로 가속
eCryptfs CBC(AES-256) CONFIG_ECRYPT_FS 파일 단위 스택 암호화 파일시스템
fscrypt (ext4/f2fs) XTS(AES-256), CTS-CBC CONFIG_FS_ENCRYPTION 파일시스템 레벨 암호화. 파일명은 CTS-CBC, 데이터는 XTS
TCP-AO CMAC(AES-128) CONFIG_TCP_AO TCP 인증 옵션(RFC 5925). BGP 세션 보호
# dm-crypt에서 AES-NI 가속 확인 (LUKS 디스크)
cryptsetup luksDump /dev/sda2 | grep cipher
# cipher: aes-xts-plain64

# IPsec GCM 설정 예시 (strongSwan)
# ike=aes256gcm16-sha384-ecp384
# esp=aes256gcm16

# 현재 사용 중인 AES 구현 확인
cat /proc/crypto | grep -A4 "name.*: xts(aes)"
# name         : xts(aes)
# driver       : xts-aes-aesni
# module       : aesni_intel
# priority     : 401

# dm-crypt I/O 중 AES-NI CPU 사용 확인
perf top -e cycles -g -- -p $(pgrep -f kcryptd)
# aesni_xts_encrypt  ← AES-NI가 사용되고 있음

AES-NI 성능 특성

# tcrypt 모듈로 AES-NI 벤치마크
modprobe tcrypt mode=500 sec=2
# mode=500대: skcipher 계열 벤치마크 (예: CBC/CTR/XTS)
# mode 번호는 커널 버전에 따라 세부 매핑이 달라질 수 있음

dmesg | tail -30
# 참고 결과 예시 (Xeon, AES-NI):
# testing speed of async cbc(aes) (cbc-aes-aesni) encryption
# test  0 (128 bit key, 16 byte blocks):  18742341 operations in 2 seconds (299877456 bytes)
# test  1 (128 bit key, 64 byte blocks):  12952847 operations in 2 seconds (828982208 bytes)
# test  2 (128 bit key, 256 byte blocks):  7348142 operations in 2 seconds (1881124352 bytes)
# test  3 (128 bit key, 1024 byte blocks): 3128904 operations in 2 seconds (3203997696 bytes)
# test  4 (128 bit key, 1472 byte blocks): 2352961 operations in 2 seconds (3463558592 bytes)
# test  5 (128 bit key, 8192 byte blocks):  537420 operations in 2 seconds (4402585600 bytes)
#                                                          → ~2.2 GB/s (CBC, 단일 코어)
모드구현1KB 블록8KB 블록비고
ECB(AES-128)aes_generic~200 MB/s~200 MB/s순수 소프트웨어, 테이블 룩업
ECB(AES-128)aesni~3.5 GB/s~5.0 GB/sAES-NI, 4블록 인터리빙
CBC(AES-128) 암호화aesni~1.5 GB/s~2.2 GB/s직렬 체인 → 파이프라인 제한
CBC(AES-128) 복호화aesni~3.5 GB/s~4.8 GB/s복호화는 병렬 가능
CTR(AES-128)aesni~3.5 GB/s~5.0 GB/s카운터 병렬 → ECB급 성능
GCM(AES-128)aesni + clmul~3.0 GB/s~4.5 GB/sCTR + GHASH 인터리빙
XTS(AES-256)aesni~2.5 GB/s~3.5 GB/stweak 연산 + 2배 키 길이
성능 측정 시 주의:
  • 터보 부스트 영향 — AVX-512/VAES 사용 시 CPU가 다운클럭할 수 있어 순수 AES-NI 대비 의외로 느릴 수 있음. 워크로드 특성에 따라 판단 필요
  • kernel_fpu_begin() 오버헤드(Overhead) — FPU 상태 저장/복원 비용(~수백 나노초)이 있어, 16바이트 단일 블록에서는 소프트웨어 구현이 더 빠를 수 있음
  • dm-crypt 실제 성능 — I/O 스택 오버헤드(scatterlist 구성, bio 처리)로 인해 tcrypt 벤치마크의 50~70% 수준이 일반적
  • NUMA 고려 — 암호화 워커 스레드(Thread)가 데이터의 NUMA 노드와 다른 노드에서 실행되면 메모리 접근 지연(Latency)으로 성능 저하

AES-NI 보안 이점: 부채널 공격 방어

소프트웨어 AES 구현은 S-Box 테이블 룩업을 사용하는데, 이는 cache-timing 부채널 공격에 취약합니다. 공격자는 AES 연산 중 캐시(Cache) 접근 패턴을 관측하여 키를 추출할 수 있습니다:

/* 소프트웨어 AES — cache-timing 취약점 */
/* crypto/aes_generic.c 의 S-Box 테이블 룩업 */
static const u32 Te0[256] = { ... };  /* 1KB 룩업 테이블 */
static const u32 Te1[256] = { ... };
static const u32 Te2[256] = { ... };
static const u32 Te3[256] = { ... };

/* 키에 의존하는 인덱스로 테이블 접근 → 캐시 라인 접근 패턴 노출 */
s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ ...;
/* ↑ 공격자가 cache-line hit/miss 패턴으로 t0, t1 값을 추론 가능 */

/* AES-NI — 하드웨어 경로로 cache-timing 노출면을 축소 */
/* AESENC 명령어는 CPU 내부 회로에서 S-Box를 수행하므로
 * 소프트웨어 테이블 룩업 대비 메모리 접근 패턴 노출이 줄어듦
 * 전체 부채널 안전성은 마이크로아키텍처/플랫폼 조건을 함께 검토해야 함 */
aesenc %xmm_key, %xmm_state   /* 실제 사이클 특성은 구현 경로와 환경에 따라 달라질 수 있음 */
💡

FIPS 및 보안 권장: 보안에 민감한 환경에서는 aes_generic(소프트웨어)이 fallback으로 사용되지 않도록 aesni_intel 모듈이 확실히 로드되었는지 확인하세요. cat /proc/crypto | grep -B1 "aes-aesni"로 확인 가능합니다. 가상 머신(VM)에서는 호스트가 AES-NI CPUID 플래그를 패스스루하는지 확인해야 합니다(QEMU: -cpu host 또는 +aes).

Intel® QuickAssist Technology (Intel® QAT)

Intel QAT는 암호화(Cipher), 해싱(Hash), 압축/해제(Compression)를 하드웨어로 가속하는 기술입니다. 커널 Crypto Framework (Crypto API)에 통합되어 IPsec, TLS(kTLS), dm-crypt, 스토리지 압축 등에서 CPU 오프로드를 제공합니다.

QAT 아키텍처

Application Kernel Crypto API / kTLS QAT Driver (qat_4xxx, qat_c62x) QAT HW Engine AE (Accel Engine) Ring Pairs Request/Response SRIOV VF (VM 직접 접근)
QAT 세대디바이스커널 드라이버지원 기능
QAT 1.x DH895xCC, C3xxx, C62x qat_dh895xcc, qat_c62x Crypto (AES, SHA, RSA), 압축 (Deflate)
QAT 2.0 (4xxx) 4xxx (Sapphire Rapids 내장) qat_4xxx Crypto + 압축 + SM2/SM3/SM4 + 향상된 RSA
QAT 연동 서비스 IPsec (ESP), kTLS (kernel TLS offload), dm-crypt, zswap 압축, DPDK crypto

QAT 커널 드라이버 설정

# QAT 디바이스 확인
lspci -d :4940   # QAT 4xxx (Sapphire Rapids)
lspci -d :37c8   # QAT C62x

# QAT 드라이버 로드
modprobe qat_4xxx       # 또는 qat_c62x, qat_dh895xcc
modprobe intel_qat       # 공통 프레임워크

# QAT 서비스 구성 (crypto, compression, 또는 둘 다)
# /etc/4xxx_dev0.conf 설정 파일
# ServicesEnabled = cy;dc  (cy=crypto, dc=data compression)

# QAT 인스턴스 확인
cat /sys/kernel/debug/qat_4xxx_0000:6b:00.0/fw_counters

# QAT가 crypto API에 등록된 알고리즘 확인
cat /proc/crypto | grep -A5 qat
# driver: qat_aes_cbc
# priority: 4001  ← S/W(aesni=400)보다 높음 → 자동 선택

# SRIOV VF 생성 (VM에 QAT 인스턴스 직접 할당)
echo 16 > /sys/bus/pci/devices/0000:6b:00.0/sriov_numvfs

QAT + IPsec 연동

# QAT IPsec offload — ESP 암호화/복호화를 QAT로 오프로드
# strongSwan 또는 Libreswan에서 자동 감지

# IPsec SA에서 QAT 사용 확인
ip xfrm state | grep -A2 enc
# enc aes-cbc ... offload dev qat_4xxx

# 성능 비교 (예시: AES-128-CBC + SHA-256)
# SW (AES-NI):     ~10 Gbps (CPU 100%)
# QAT offload:     ~40 Gbps (CPU ~20%)

# kTLS + QAT (커널 TLS 오프로드)
# KTLS_TX: 송신 데이터를 QAT가 AES-GCM 암호화
# 커널 5.3+ kTLS + QAT crypto 드라이버 자동 연동

QAT 사용 시 주의사항

QAT 운영 시 고려사항:
  • 소량 데이터 오버헤드 — 작은 패킷(64B 이하)에서는 QAT 오프로드 지연(~10μs)이 S/W AES-NI(~0.1μs)보다 클 수 있음. 일정 크기 이상에서만 이점
  • 큐 깊이 관리 — Ring pair가 포화되면 요청이 거절됨. 비동기 API(crypto_async) 사용 시 완료 콜백(Callback)에서 에러 처리 필수
  • NUMA 인지 — QAT 디바이스가 특정 NUMA 노드에 연결. 다른 노드의 CPU가 사용하면 cross-node 메모리 접근으로 지연 증가
  • SR-IOV VF 개수 — VF당 할당되는 Ring pair 수가 줄어들어 VM별 처리량 제한. 적절한 VF 수 계획 필요
  • 펌웨어 의존성 — QAT 펌웨어 로딩이 필요하며, 커널 빌드 시 CONFIG_CRYPTO_DEV_QAT_4XXX 등 활성화 필요
  • fallback 메커니즘 — QAT 장애 시 자동으로 S/W 구현(aesni_intel)으로 fallback. priority 기반 선택

알고리즘 구현 가이드

커널 Crypto Framework (Crypto API)에 새로운 알고리즘을 추가하려면 알고리즘 구조체(Struct) 정의 → 콜백 구현 → 등록 → 테스트 벡터 추가의 과정을 거칩니다. 이 섹션에서는 각 단계를 상세히 다룹니다.

드라이버 작성자가 먼저 결정할 것: 어떤 인터페이스를 구현할 것인가

Crypto driver 작성에서 가장 먼저 해야 할 일은 "이 하드웨어가 AES를 지원한다"가 아니라 어떤 추상 인터페이스를 커널에 노출할지를 정하는 것입니다. 같은 AES 엔진이라도 단일 블록만 처리하는지, 모드(CBC/CTR/XTS/GCM)까지 자체 처리하는지, AAD와 tag를 함께 계산하는지, DMA로 긴 SG를 직접 읽는지에 따라 등록해야 하는 타입이 달라집니다.

인터페이스언제 선택하나장점주의점
struct crypto_alg + cipher_alg하드웨어가 "단일 블록 암복호화" primitive만 제공할 때가장 낮은 레벨이라 조합성이 높음대부분의 상위 사용자는 직접 raw block cipher를 안 쓰므로, 보통 skcipher/aead 래퍼를 추가로 제공해야 합니다.
struct skcipher_algCBC/CTR/XTS 같은 대칭 암호 모드를 직접 제공할 때dm-crypt, fscrypt, 네트워크 경로가 바로 사용 가능IV, alignmask, walksize, fallback 설계를 함께 해야 합니다.
struct aead_algGCM/CCM/ChaCha20-Poly1305처럼 인증 태그까지 한 번에 처리할 때IPsec, kTLS, 보안 저장 포맷과 직접 맞닿음AAD 길이와 tag 길이 규약을 틀리면 전체가 깨집니다.
struct shash_alg순수 소프트웨어 해시 또는 즉시 계산 가능한 작은 엔진구현이 가장 단순SG/DMA 친화성은 약합니다.
struct ahash_algDMA 기반 해시 엔진, 긴 SG 스트림, 비동기 완료가 필요할 때네트워크/파일/펌웨어처럼 조각 난 입력에 강함reqsize, 상태 export/import, one-shot 제약을 명확히 해야 합니다.
struct akcipher_algRSA 같은 공개키 암/복호화 또는 서명 primitive를 제공할 때키 설정, max size, 비동기 요청을 통합 제공req->dst_len 갱신 계약을 지켜야 합니다.
struct kpp_algDH/ECDH처럼 공개키 생성과 공유 비밀 계산을 제공할 때핸드셰이크 계층과 직접 연결됨packed secret 형식과 공개키 출력 길이 계산이 중요합니다.
struct rng_algTRNG/DRBG 엔진을 제공할 때FIPS 경계, 정책형 RNG 선택에 유리seedsize와 재시드 의미를 명확히 해야 합니다.
struct acomp_alg압축 가속기를 비동기로 제공할 때압축 엔진도 Crypto queue/model을 재사용 가능dst == NULL일 때 출력 버퍼 할당/해제를 정확히 구현해야 합니다.
struct crypto_template기존 알고리즘을 감싸 새 이름을 동적으로 만들 때한 구현으로 다수의 조합 인스턴스를 만들 수 있음spawn, 이름 조합, instance free 경로를 잘못 짜면 해제 경합(Contention)이 생깁니다.
실무 기준: 하드웨어가 AES round primitive만 준다고 해서 곧바로 skcipher를 구현할 필요는 없습니다. 어떤 엔진은 먼저 raw block cipher를 안정적으로 올리고, 그 위에 CBC/XTS/GCM glue를 얹는 편이 유지보수에 더 좋습니다. 반대로 하드웨어가 GCM tag까지 완전히 계산한다면 raw block layer를 굳이 외부에 노출하지 않고 aead만 등록하는 편이 더 자연스럽습니다.

struct crypto_alg — 알고리즘 등록의 핵심

모든 암호화 알고리즘은 struct crypto_alg을 기반으로 등록됩니다. 이 구조체는 알고리즘의 메타데이터와 구현 콜백을 담는 컨테이너(Container)입니다:

/* include/linux/crypto.h */
struct crypto_alg {
    struct list_head     cra_list;        /* 내부 연결 리스트 */
    struct list_head     cra_users;       /* 이 알고리즘을 사용하는 tfm 목록 */

    u32                  cra_flags;       /* 알고리즘 속성 플래그 */
    unsigned int         cra_blocksize;   /* 블록 크기 (해시: 입력 블록, 암호: 블록 크기) */
    unsigned int         cra_ctxsize;     /* tfm 컨텍스트 크기 (키 상태 등) */
    unsigned int         cra_alignmask;   /* 데이터 정렬 요구사항 (0 = 1바이트 정렬) */

    int                  cra_priority;    /* 우선순위: 높을수록 먼저 선택 */
    unsigned int         cra_refcnt;      /* 참조 카운터 */

    char                 cra_name[128];       /* 알고리즘 정규 이름 ("sha256") */
    char                 cra_driver_name[128]; /* 드라이버 고유 이름 ("sha256-avx2") */

    const struct crypto_type *cra_type;    /* 알고리즘 유형 (skcipher, shash 등) */

    union {
        struct cipher_alg cipher;         /* 단일 블록 암호 (raw block cipher) */
    } cra_u;

    int (*cra_init)(struct crypto_tfm *tfm);    /* tfm 생성 시 초기화 */
    void (*cra_exit)(struct crypto_tfm *tfm);   /* tfm 해제 시 정리 */
    void (*cra_destroy)(struct crypto_alg *alg); /* 알고리즘 등록 해제 시 */

    struct module        *cra_module;     /* 소속 커널 모듈 */
};
코드 설명

include/linux/crypto.h에 정의된 struct crypto_alg는 커널 Crypto API에 등록되는 모든 암호 알고리즘의 기본 메타데이터 컨테이너입니다.

  • cra_list / cra_users전역 알고리즘 리스트(crypto_alg_list)와 이 알고리즘으로 생성된 tfm 인스턴스 목록을 연결하는 내부 리스트 헤드입니다.
  • cra_flagsCRYPTO_ALG_ASYNC(비동기), CRYPTO_ALG_NEED_FALLBACK(S/W 대체 필요), CRYPTO_ALG_INTERNAL(내부 전용) 등 알고리즘 속성을 비트마스크로 표현합니다.
  • cra_ctxsizecrypto_alloc_*() 호출 시 tfm에 함께 할당될 드라이버별 컨텍스트 크기입니다. 키 스케줄, H/W 핸들 등을 저장하는 공간으로 사용됩니다.
  • cra_priority같은 cra_name을 가진 여러 구현 중 이 값이 가장 높은 것이 자동 선택됩니다. generic(100) < SIMD(300) < H/W(4001) 순이 일반적입니다.
  • cra_name / cra_driver_namecra_name은 소비자가 요청하는 표준 이름("sha256"), cra_driver_name은 구현체 고유 식별자("sha256-avx2")입니다.
  • cra_init / cra_exittfm 인스턴스 생성·해제 시 호출되는 콜백으로, fallback tfm 할당이나 H/W 리소스 초기화·정리에 사용됩니다.
  • cra_module이 알고리즘이 속한 커널 모듈 포인터로, tfm이 존재하는 동안 모듈 언로드를 방지하는 참조 카운팅에 활용됩니다.

주요 필드 상세

필드설명예시 값
cra_name사용자가 요청하는 정규 이름. 같은 이름의 여러 구현이 공존 가능"sha256", "aes"
cra_driver_name구현체를 고유하게 식별하는 이름"sha256-avx2", "aes-aesni"
cra_priority같은 cra_name 중 가장 높은 priority가 선택됨generic: 100, AES-NI: 400, QAT: 4001
cra_flags알고리즘 속성 비트마스크CRYPTO_ALG_ASYNC, CRYPTO_ALG_NEED_FALLBACK
cra_blocksize처리 단위 크기 (바이트)AES: 16, SHA-256: 64, 스트림 암호: 1
cra_ctxsizetfm당 할당할 private 컨텍스트 크기sizeof(struct my_alg_ctx)
cra_alignmask데이터 정렬 마스크. 예: 0x3이면 4바이트 정렬 필요보통 0 (S/W) 또는 0xf (H/W)

cra_flags 주요 플래그

플래그설명
CRYPTO_ALG_ASYNC비동기 알고리즘. 완료 콜백으로 결과를 통보. H/W 가속기에서 주로 사용
CRYPTO_ALG_NEED_FALLBACK일부 입력을 처리하지 못할 때 S/W fallback이 필요함을 표시
CRYPTO_ALG_KERN_DRIVER_ONLY커널 내부 드라이버 전용 구현. ISA나 사용자 공간 인터페이스로 직접 노출하는 것을 막고 싶을 때 사용
CRYPTO_ALG_INTERNAL내부 전용 알고리즘. AF_ALG(사용자 공간)에 노출되지 않음
CRYPTO_ALG_ALLOCATES_MEMORY요청 처리 중 메모리 할당이 일어날 수 있음을 의미. alignmask/page 경계 제약을 만족하지 못하면 re-align/임시 버퍼가 생길 수 있음
CRYPTO_ALG_FIPS_INTERNAL자체로는 승인 알고리즘이 아니지만 FIPS 승인 상위 알고리즘의 내부 부품으로만 쓰이게 할 때 사용
CRYPTO_ALG_TESTEDtestmgr 자가 테스트를 통과한 뒤 커널이 세우는 내부 상태 비트. 드라이버가 직접 넣는 플래그가 아닙니다.
CRYPTO_ALG_OPTIONAL_KEY키 설정이 선택사항 (예: 키 없는 해시)
CRYPTO_ALG_DEAD해제 진행 중인 알고리즘 (내부 사용)

이름, 우선순위, 모듈 alias를 어떻게 설계할 것인가

실제 driver author 입장에서 가장 먼저 엉키는 지점은 연산 코드가 아니라 이름 체계와 노출 정책입니다. cra_name은 커널 전체가 공유하는 공개 계약이고, cra_driver_name은 특정 구현체를 식별하는 내부 이름입니다. 이 둘을 섞어 쓰면 autoload, fallback, testmgr, 우선순위 선택이 전부 헷갈립니다.

항목권장 기준잘못 설계했을 때의 문제
cra_name소비자가 요청할 표준 이름을 그대로 사용합니다. 예: "ctr(aes)", "sha256", "gcm(aes)"벤더명을 섞어 "mychip-gcm"처럼 만들면 상위 subsystem이 해당 구현을 자동 선택하지 못합니다.
cra_driver_name벤더/ISA/세부 구현을 식별하도록 구체적으로 씁니다. 예: "gcm-aes-myhw", "sha256-avx2"driver name이 모호하면 /proc/crypto, 로그, 자가 테스트 실패 메시지에서 원인 추적이 어려워집니다.
내부 helper 이름직접 소비시키고 싶지 않은 primitive는 "__ctr(aes)"처럼 내부 이름을 따로 두고 CRYPTO_ALG_INTERNAL 또는 CRYPTO_ALG_KERN_DRIVER_ONLY로 숨깁니다.외부에 노출되면 AF_ALG나 다른 subsystem이 아직 완성되지 않은 내부 primitive를 직접 잡아 버릴 수 있습니다.
cra_priority같은 cra_name을 가진 다른 구현과의 상대 순서만 의미 있습니다. generic보다 조금 높게, SIMD/HW보다 낮거나 높게 일관되게 정합니다.과도하게 높게 잡으면 fallback보다 미완성 H/W 경로가 먼저 선택되고, 너무 낮으면 등록은 됐는데 영원히 선택되지 않습니다.
MODULE_ALIAS_CRYPTO최소한 공개 이름과 driver name 둘 다 alias를 추가합니다.모듈 자동 로드가 안 되면 crypto_alloc_*()가 "알고리즘 없음"으로 끝나고 사용자는 원인을 못 찾습니다.
#include <crypto/internal/skcipher.h>

static struct skcipher_alg my_ctr_algs[] = {
    {
        .setkey    = my_ctr_setkey,
        .encrypt   = my_ctr_encrypt_internal,
        .decrypt   = my_ctr_decrypt_internal,
        .init      = my_ctr_init_tfm,
        .exit      = my_ctr_exit_tfm,
        .min_keysize = 16,
        .max_keysize = 32,
        .ivsize      = 16,
        .chunksize   = 16,
        .walksize    = 16,
        .base = {
            .cra_name        = "__ctr(aes)",
            .cra_driver_name = "__ctr-aes-myhw",
            .cra_priority    = 400,
            .cra_flags       = CRYPTO_ALG_ASYNC |
                               CRYPTO_ALG_INTERNAL |
                               CRYPTO_ALG_KERN_DRIVER_ONLY,
            .cra_blocksize   = 1,
            .cra_ctxsize     = sizeof(struct my_ctr_ctx),
            .cra_module      = THIS_MODULE,
        },
    }, {
        .setkey    = my_ctr_setkey,
        .encrypt   = my_ctr_encrypt,
        .decrypt   = my_ctr_decrypt,
        .init      = my_ctr_init_tfm,
        .exit      = my_ctr_exit_tfm,
        .min_keysize = 16,
        .max_keysize = 32,
        .ivsize      = 16,
        .chunksize   = 16,
        .walksize    = 16,
        .base = {
            .cra_name        = "ctr(aes)",
            .cra_driver_name = "ctr-aes-myhw",
            .cra_priority    = 401,
            .cra_flags       = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK,
            .cra_blocksize   = 1,
            .cra_ctxsize     = sizeof(struct my_ctr_ctx),
            .cra_module      = THIS_MODULE,
        },
    },
};

MODULE_ALIAS_CRYPTO("ctr(aes)");
MODULE_ALIAS_CRYPTO("ctr-aes-myhw");
코드 설명

crypto/internal/skcipher.h 기반의 struct skcipher_alg 배열로, 하나의 H/W 드라이버가 내부용과 외부용 두 가지 알고리즘을 동시에 등록하는 패턴입니다.

  • 첫 번째 항목 (__ctr(aes))cra_name"__ctr(aes)"로 시작하는 이중 밑줄 접두사는 내부 전용 primitive를 의미합니다. CRYPTO_ALG_INTERNAL | CRYPTO_ALG_KERN_DRIVER_ONLY 플래그로 AF_ALG 등 외부 접근을 차단합니다.
  • 두 번째 항목 (ctr(aes))외부에 노출되는 공개 알고리즘으로, CRYPTO_ALG_NEED_FALLBACK 플래그를 설정하여 SIMD 불가 상황에서 S/W 대체를 허용합니다. priority 401은 내부 알고리즘(400)보다 1 높게 설정됩니다.
  • setkey / encrypt / decryptskcipher_alg의 핵심 콜백들로, 키 설정과 암·복호화 연산을 구현합니다. 내부/외부 알고리즘이 서로 다른 함수(my_ctr_encrypt_internal vs my_ctr_encrypt)를 지정할 수 있습니다.
  • min_keysize / max_keysize / ivsizeAES-128/192/256을 지원하기 위해 키 크기 범위(16~32바이트)와 CTR 모드의 IV 크기(16바이트)를 지정합니다.
  • MODULE_ALIAS_CRYPTO공개 이름과 드라이버 이름 모두에 대해 모듈 alias를 등록하여, crypto_alloc_skcipher("ctr(aes)") 호출 시 커널이 자동으로 이 모듈을 modprobe할 수 있게 합니다.
Template/wrapper 작성자 주의: include/crypto/algapi.hCRYPTO_ALG_ASYNC, CRYPTO_ALG_NEED_FALLBACK, CRYPTO_ALG_ALLOCATES_MEMORY를 "상속돼야 하는 플래그"로 다룹니다. 내부 알고리즘을 감싸는 템플릿이나 래퍼를 만들 때 이 플래그를 지워 버리면 상위 계층이 잘못된 실행 문맥을 가정하게 됩니다.

shash 알고리즘 구현 (동기 해시)

가장 단순한 구현 유형인 shash (synchronous hash)로 시작합니다. 커널 소스의 crypto/sha256_generic.c를 참고한 전체 구현 예제입니다:

#include <crypto/internal/hash.h>
#include <linux/module.h>

#define MY_HASH_DIGEST_SIZE  32   /* 출력 해시 크기 (바이트) */
#define MY_HASH_BLOCK_SIZE   64   /* 내부 처리 블록 크기 */

/* tfm당 할당되는 컨텍스트 (키, 상태 등) */
struct my_hash_tfm_ctx {
    u8 key[32];
    bool has_key;
};

/* 요청(desc)당 할당되는 상태 (중간 해시 상태 등) */
struct my_hash_desc_ctx {
    u64 state[4];
    u8  buf[MY_HASH_BLOCK_SIZE];
    unsigned int buflen;
    u64 count;        /* 처리한 총 바이트 수 */
};

/* ① 해시 초기화: 내부 상태를 초기값으로 설정 */
static int my_hash_init(struct shash_desc *desc)
{
    struct my_hash_desc_ctx *dctx = shash_desc_ctx(desc);

    dctx->state[0] = 0x6a09e667f3bcc908ULL;  /* 초기 해시 값 */
    dctx->state[1] = 0xbb67ae8584caa73bULL;
    dctx->state[2] = 0x3c6ef372fe94f82bULL;
    dctx->state[3] = 0xa54ff53a5f1d36f1ULL;
    dctx->buflen = 0;
    dctx->count = 0;
    return 0;
}

/* ② 데이터 입력: 여러 번 호출될 수 있음 (스트리밍 해시) */
static int my_hash_update(struct shash_desc *desc,
                          const u8 *data, unsigned int len)
{
    struct my_hash_desc_ctx *dctx = shash_desc_ctx(desc);

    dctx->count += len;

    /* 버퍼에 남은 데이터와 새 데이터를 합쳐 블록 단위로 처리 */
    if (dctx->buflen + len < MY_HASH_BLOCK_SIZE) {
        memcpy(dctx->buf + dctx->buflen, data, len);
        dctx->buflen += len;
        return 0;
    }

    /* 블록 단위 처리 (실제 해시 압축 함수 호출) */
    /* my_hash_compress(dctx->state, data_blocks, nblocks); */
    return 0;
}

/* ③ 최종 해시 값 출력 */
static int my_hash_final(struct shash_desc *desc, u8 *out)
{
    struct my_hash_desc_ctx *dctx = shash_desc_ctx(desc);

    /* 패딩 적용 + 마지막 블록 처리 */
    /* my_hash_final_block(dctx); */

    /* 내부 상태를 출력 형식으로 변환 */
    memcpy(out, dctx->state, MY_HASH_DIGEST_SIZE);
    return 0;
}

/* ④ 선택사항: 한 번에 처리 (init+update+final 최적화) */
static int my_hash_digest(struct shash_desc *desc,
                          const u8 *data, unsigned int len,
                          u8 *out)
{
    my_hash_init(desc);
    my_hash_update(desc, data, len);
    return my_hash_final(desc, out);
}

/* ⑤ 선택사항: 키 설정 (HMAC, keyed hash용) */
static int my_hash_setkey(struct crypto_shash *tfm,
                          const u8 *key, unsigned int keylen)
{
    struct my_hash_tfm_ctx *ctx = crypto_shash_ctx(tfm);

    if (keylen > 32)
        return -EINVAL;

    memcpy(ctx->key, key, keylen);
    ctx->has_key = true;
    return 0;
}

/* ⑥ 상태 import/export (중간 상태 저장/복원, 선택사항) */
static int my_hash_export(struct shash_desc *desc, void *out)
{
    struct my_hash_desc_ctx *dctx = shash_desc_ctx(desc);
    memcpy(out, dctx, sizeof(*dctx));
    return 0;
}

static int my_hash_import(struct shash_desc *desc, const void *in)
{
    struct my_hash_desc_ctx *dctx = shash_desc_ctx(desc);
    memcpy(dctx, in, sizeof(*dctx));
    return 0;
}

/* ⑦ shash_alg 구조체 정의 */
static struct shash_alg my_hash_alg = {
    .init        = my_hash_init,
    .update      = my_hash_update,
    .final       = my_hash_final,
    .digest      = my_hash_digest,       /* 선택: 한 번에 처리 */
    .setkey      = my_hash_setkey,       /* 선택: keyed hash */
    .export      = my_hash_export,       /* 선택: 상태 저장 */
    .import      = my_hash_import,       /* 선택: 상태 복원 */
    .descsize    = sizeof(struct my_hash_desc_ctx),
    .statesize   = sizeof(struct my_hash_desc_ctx),
    .digestsize  = MY_HASH_DIGEST_SIZE,
    .base        = {
        .cra_name        = "myhash256",
        .cra_driver_name = "myhash256-generic",
        .cra_priority    = 100,
        .cra_blocksize   = MY_HASH_BLOCK_SIZE,
        .cra_ctxsize     = sizeof(struct my_hash_tfm_ctx),
        .cra_module      = THIS_MODULE,
    }
};

/* ⑧ 모듈 초기화/해제 */
static int __init my_hash_mod_init(void)
{
    return crypto_register_shash(&my_hash_alg);
}

static void __exit my_hash_mod_exit(void)
{
    crypto_unregister_shash(&my_hash_alg);
}

module_init(my_hash_mod_init);
module_exit(my_hash_mod_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example hash algorithm");
MODULE_ALIAS_CRYPTO("myhash256");
MODULE_ALIAS_CRYPTO("myhash256-generic");
💡

shash vs ahash: shash는 동기(synchronous) 해시로 구현이 단순합니다. S/W 구현은 shash를 사용하세요. ahash는 비동기(async) 해시로, H/W 가속기처럼 처리 완료까지 대기가 필요한 경우 사용합니다. 커널은 내부적으로 shash를 ahash로 자동 래핑하여 비동기 인터페이스에서도 사용할 수 있게 합니다.

shash 콜백 호출 흐름

shash 콜백 호출 순서 init() update() chunk 1 update() chunk N final() digest() = init+update+final ... (반복 가능) ...
스트리밍 해시: init → update × N → final | 한 번에: digest

skcipher 알고리즘 구현 (대칭 암호)

대칭 블록/스트림 암호의 구현입니다. struct skcipher_alg을 사용하여 등록합니다:

#include <crypto/internal/skcipher.h>
#include <linux/module.h>

#define MY_CIPHER_BLOCK_SIZE  16
#define MY_CIPHER_KEY_SIZE    32
#define MY_CIPHER_IV_SIZE     16

/* tfm 컨텍스트: 확장된 키 스케줄 등 */
struct my_cipher_ctx {
    u32 enc_key_sched[60];     /* 암호화 키 스케줄 */
    u32 dec_key_sched[60];     /* 복호화 키 스케줄 */
    unsigned int key_length;
};

/* 키 설정: 키 스케줄(라운드 키) 생성 */
static int my_cipher_setkey(struct crypto_skcipher *tfm,
                            const u8 *key, unsigned int keylen)
{
    struct my_cipher_ctx *ctx = crypto_skcipher_ctx(tfm);

    if (keylen != 16 && keylen != 24 && keylen != 32)
        return -EINVAL;

    ctx->key_length = keylen;
    /* 키 스케줄 확장 수행 */
    /* my_expand_key(ctx->enc_key_sched, key, keylen); */
    /* my_expand_key_dec(ctx->dec_key_sched, key, keylen); */
    return 0;
}

/* 암호화: scatterlist 기반 데이터 처리 */
static int my_cipher_encrypt(struct skcipher_request *req)
{
    struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
    struct my_cipher_ctx *ctx = crypto_skcipher_ctx(tfm);
    struct skcipher_walk walk;
    int err;

    /* skcipher_walk: scatterlist를 선형 버퍼로 순회하는 헬퍼 */
    err = skcipher_walk_virt(&walk, req, false);

    while (walk.nbytes) {
        unsigned int nbytes = walk.nbytes;
        const u8 *src = walk.src.virt.addr;
        u8 *dst = walk.dst.virt.addr;
        u8 *iv = walk.iv;

        /* 블록 단위로 CBC 암호화 수행 */
        while (nbytes >= MY_CIPHER_BLOCK_SIZE) {
            /* XOR plaintext with IV/previous ciphertext (CBC 모드) */
            crypto_xor_cpy(dst, src, iv, MY_CIPHER_BLOCK_SIZE);
            /* 블록 암호화 */
            /* my_encrypt_block(ctx->enc_key_sched, dst, dst); */
            iv = dst;
            src += MY_CIPHER_BLOCK_SIZE;
            dst += MY_CIPHER_BLOCK_SIZE;
            nbytes -= MY_CIPHER_BLOCK_SIZE;
        }

        /* IV 업데이트 (다음 walk 반복을 위해) */
        memcpy(walk.iv, iv, MY_CIPHER_IV_SIZE);
        err = skcipher_walk_done(&walk, nbytes);
    }

    return err;
}

/* 복호화 */
static int my_cipher_decrypt(struct skcipher_request *req)
{
    /* 암호화와 유사하나 역방향 처리 + 복호화 키 스케줄 사용 */
    /* ... */
    return 0;
}

static struct skcipher_alg my_cipher_alg = {
    .setkey      = my_cipher_setkey,
    .encrypt     = my_cipher_encrypt,
    .decrypt     = my_cipher_decrypt,
    .min_keysize = 16,
    .max_keysize = 32,
    .ivsize      = MY_CIPHER_IV_SIZE,
    .chunksize   = MY_CIPHER_BLOCK_SIZE,  /* 최소 처리 단위 */
    .walksize    = MY_CIPHER_BLOCK_SIZE,  /* walk 반복당 최소 크기 */
    .base        = {
        .cra_name        = "cbc(mycipher)",
        .cra_driver_name = "cbc-mycipher-generic",
        .cra_priority    = 100,
        .cra_flags       = CRYPTO_ALG_ASYNC,  /* H/W인 경우 */
        .cra_blocksize   = MY_CIPHER_BLOCK_SIZE,
        .cra_ctxsize     = sizeof(struct my_cipher_ctx),
        .cra_module      = THIS_MODULE,
    }
};

static int __init my_cipher_mod_init(void)
{
    return crypto_register_skcipher(&my_cipher_alg);
}

static void __exit my_cipher_mod_exit(void)
{
    crypto_unregister_skcipher(&my_cipher_alg);
}

module_init(my_cipher_mod_init);
module_exit(my_cipher_mod_exit);
MODULE_LICENSE("GPL");
MODULE_ALIAS_CRYPTO("cbc(mycipher)");

skcipher_walk 상세

skcipher_walk은 scatterlist 기반의 분산된 데이터를 선형 버퍼처럼 순회하는 핵심 헬퍼입니다:

함수설명사용 맥락
skcipher_walk_virt()가상 주소(Virtual Address) 기반 walk 시작일반적인 S/W 구현
skcipher_walk_async()비동기 요청 컨텍스트에서 walk 시작DMA/오프로드 구현
skcipher_walk_done()현재 chunk 완료, 다음으로 이동반복 루프에서 호출
walk.nbytes현재 chunk에서 처리 가능한 바이트 수반복 조건
walk.src.virt.addr입력 데이터 포인터소스 읽기
walk.dst.virt.addr출력 데이터 포인터결과 쓰기
walk.iv현재 IV 포인터IV 업데이트
ℹ️

scatterlist와 zero-copy: 커널의 암호 데이터는 물리적으로 연속되지 않은 페이지에 분산되어 있을 수 있습니다. skcipher_walk은 이런 scatterlist를 임시 선형 버퍼로 매핑(Mapping)하거나, 이미 연속된 경우 직접 포인터를 제공하여 불필요한 복사를 방지합니다.

AEAD 알고리즘 구현

AEAD(Authenticated Encryption with Associated Data)는 암호화와 무결성 검증을 동시에 수행합니다. 구현은 struct aead_alg을 사용합니다:

#include <crypto/internal/aead.h>
#include <crypto/scatterwalk.h>

struct my_aead_ctx {
    struct crypto_skcipher *enc_tfm;  /* 내부 암호화 tfm */
    struct crypto_shash    *mac_tfm;  /* 내부 MAC tfm */
    unsigned int authsize;
};

static int my_aead_setkey(struct crypto_aead *tfm,
                          const u8 *key, unsigned int keylen)
{
    struct my_aead_ctx *ctx = crypto_aead_ctx(tfm);

    /* 키를 암호화용과 MAC용으로 분리 */
    struct crypto_authenc_keys keys;
    if (crypto_authenc_extractkeys(&keys, key, keylen))
        return -EINVAL;

    /* 각 내부 알고리즘에 키 설정 */
    crypto_skcipher_setkey(ctx->enc_tfm, keys.enckey, keys.enckeylen);
    crypto_shash_setkey(ctx->mac_tfm, keys.authkey, keys.authkeylen);

    return 0;
}

static int my_aead_setauthsize(struct crypto_aead *tfm,
                               unsigned int authsize)
{
    struct my_aead_ctx *ctx = crypto_aead_ctx(tfm);

    if (authsize > 16)
        return -EINVAL;

    ctx->authsize = authsize;
    return 0;
}

static int my_aead_encrypt(struct aead_request *req)
{
    struct crypto_aead *tfm = crypto_aead_reqtfm(req);
    struct my_aead_ctx *ctx = crypto_aead_ctx(tfm);

    /* 1단계: 평문을 암호화 (cbc, ctr 등) */
    /* 2단계: AAD + 암호문에 대해 MAC 계산 */
    /* 3단계: 인증 태그를 출력 끝에 추가 */

    /* req->src: [AAD | 평문]
     * req->dst: [AAD | 암호문 | 인증태그]
     * req->assoclen: AAD 길이
     * req->cryptlen: 평문 길이
     */
    return 0;
}

static int my_aead_decrypt(struct aead_request *req)
{
    /* 1단계: AAD + 암호문에 대해 MAC 재계산
     * 2단계: 수신된 인증 태그와 비교
     * 3단계: 일치하면 복호화, 불일치시 -EBADMSG 반환
     *
     * req->src: [AAD | 암호문 | 인증태그]
     * req->cryptlen: 암호문 + 인증태그 길이
     * 실제 암호문 길이 = req->cryptlen - ctx->authsize
     */
    return 0;
}

static int my_aead_init_tfm(struct crypto_aead *tfm)
{
    struct my_aead_ctx *ctx = crypto_aead_ctx(tfm);

    /* 내부 알고리즘 할당 */
    ctx->enc_tfm = crypto_alloc_skcipher("cbc(aes)", 0, 0);
    if (IS_ERR(ctx->enc_tfm))
        return PTR_ERR(ctx->enc_tfm);

    ctx->mac_tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
    if (IS_ERR(ctx->mac_tfm)) {
        crypto_free_skcipher(ctx->enc_tfm);
        return PTR_ERR(ctx->mac_tfm);
    }

    /* AEAD 요청 크기 설정: 내부 skcipher 요청도 포함 */
    crypto_aead_set_reqsize(tfm,
        sizeof(struct skcipher_request) +
        crypto_skcipher_reqsize(ctx->enc_tfm));

    return 0;
}

static void my_aead_exit_tfm(struct crypto_aead *tfm)
{
    struct my_aead_ctx *ctx = crypto_aead_ctx(tfm);

    crypto_free_skcipher(ctx->enc_tfm);
    crypto_free_shash(ctx->mac_tfm);
}

static struct aead_alg my_aead_alg = {
    .setkey      = my_aead_setkey,
    .setauthsize = my_aead_setauthsize,
    .encrypt     = my_aead_encrypt,
    .decrypt     = my_aead_decrypt,
    .init        = my_aead_init_tfm,
    .exit        = my_aead_exit_tfm,
    .ivsize      = 16,
    .maxauthsize = 16,
    .chunksize   = 16,
    .base        = {
        .cra_name        = "authenc(hmac(sha256),cbc(aes))",
        .cra_driver_name = "my-authenc-hmac-sha256-cbc-aes",
        .cra_priority    = 100,
        .cra_flags       = CRYPTO_ALG_ASYNC,
        .cra_blocksize   = 1,
        .cra_ctxsize     = sizeof(struct my_aead_ctx),
        .cra_module      = THIS_MODULE,
    }
};
AEAD scatterlist 레이아웃 주의:

AEAD의 scatterlist는 [AAD | 데이터 | 인증태그] 순서입니다. 암호화 시 req->cryptlen은 평문 길이이고 출력에 인증태그가 추가됩니다. 복호화 시 req->cryptlen암호문 + 인증태그 길이이므로, 실제 암호문 길이는 req->cryptlen - authsize입니다. 이 비대칭적 규약을 잘못 구현하면 데이터 손상이 발생합니다.

AEAD 하드웨어 드라이버 구현: aead_engine_alg 기반 GCM 예시

위 예시는 "기존 암호 + MAC 조합"을 직접 보여 주기 위한 것입니다. 하지만 실제 SoC/PCIe 가속기는 대개 GCM/CCM tag 생성과 검증을 한 번에 처리합니다. 이런 장치를 driver로 올릴 때는 aead_engine_algcrypto_transfer_aead_request_to_engine() 패턴이 가장 실용적입니다. 핵심은 요청 레이아웃을 그대로 하드웨어 명령으로 번역하고, 하드웨어가 못 받는 입력만 fallback으로 보내는 것입니다.

#include <crypto/internal/aead.h>
#include <crypto/engine.h>

struct my_gcm_dev {
    struct device *dev;
    struct crypto_engine *engine;
    struct aead_request *active_req;
};

struct my_gcm_tfm_ctx {
    struct my_gcm_dev *dd;
    struct crypto_aead *fallback;
    unsigned int authsize;
    u8 key[32];
    unsigned int keylen;
};

struct my_gcm_reqctx {
    dma_addr_t src_dma;
    dma_addr_t dst_dma;
    u8 iv[16];
    bool decrypt;
    bool mapped;
};

static inline struct aead_request *my_gcm_fallback_req(struct my_gcm_reqctx *rctx)
{
    return PTR_ALIGN(rctx + 1, crypto_tfm_ctx_alignment());
}

static int my_gcm_setkey(struct crypto_aead *tfm,
                         const u8 *key, unsigned int keylen)
{
    struct my_gcm_tfm_ctx *ctx = crypto_aead_ctx(tfm);

    if (keylen != 16 && keylen != 24 && keylen != 32)
        return -EINVAL;

    memcpy(ctx->key, key, keylen);
    ctx->keylen = keylen;
    return crypto_aead_setkey(ctx->fallback, key, keylen);
}

static int my_gcm_setauthsize(struct crypto_aead *tfm,
                              unsigned int authsize)
{
    struct my_gcm_tfm_ctx *ctx = crypto_aead_ctx(tfm);

    switch (authsize) {
    case 8:
    case 12:
    case 16:
        ctx->authsize = authsize;
        return crypto_aead_setauthsize(ctx->fallback, authsize);
    }

    return -EINVAL;
}

static int my_gcm_init_tfm(struct crypto_aead *tfm)
{
    struct my_gcm_tfm_ctx *ctx = crypto_aead_ctx(tfm);

    ctx->fallback = crypto_alloc_aead("gcm(aes-generic)", 0, 0);
    if (IS_ERR(ctx->fallback))
        return PTR_ERR(ctx->fallback);

    ctx->authsize = 16;
    crypto_aead_setauthsize(ctx->fallback, ctx->authsize);
    crypto_aead_set_reqsize_dma(tfm,
        sizeof(struct my_gcm_reqctx) +
        crypto_tfm_ctx_alignment() +
        sizeof(struct aead_request) +
        crypto_aead_reqsize(ctx->fallback));
    return 0;
}

static void my_gcm_exit_tfm(struct crypto_aead *tfm)
{
    struct my_gcm_tfm_ctx *ctx = crypto_aead_ctx(tfm);
    crypto_free_aead(ctx->fallback);
}

static int my_gcm_do_fallback(struct aead_request *req, bool decrypt)
{
    struct crypto_aead *tfm = crypto_aead_reqtfm(req);
    struct my_gcm_tfm_ctx *ctx = crypto_aead_ctx(tfm);
    struct my_gcm_reqctx *rctx = aead_request_ctx(req);
    struct aead_request *subreq = my_gcm_fallback_req(rctx);

    aead_request_set_tfm(subreq, ctx->fallback);
    aead_request_set_callback(subreq, req->base.flags,
                              req->base.complete, req->base.data);
    aead_request_set_crypt(subreq, req->src, req->dst,
                           req->cryptlen, req->iv);
    aead_request_set_ad(subreq, req->assoclen);

    if (decrypt)
        return crypto_aead_decrypt(subreq);
    return crypto_aead_encrypt(subreq);
}

static int my_gcm_encrypt(struct aead_request *req)
{
    struct crypto_aead *tfm = crypto_aead_reqtfm(req);
    struct my_gcm_tfm_ctx *ctx = crypto_aead_ctx(tfm);
    struct my_gcm_reqctx *rctx = aead_request_ctx(req);

    rctx->decrypt = false;
    if (!my_gcm_hw_can_handle(req))
        return my_gcm_do_fallback(req, false);

    return crypto_transfer_aead_request_to_engine(ctx->dd->engine, req);
}

static int my_gcm_decrypt(struct aead_request *req)
{
    struct crypto_aead *tfm = crypto_aead_reqtfm(req);
    struct my_gcm_tfm_ctx *ctx = crypto_aead_ctx(tfm);
    struct my_gcm_reqctx *rctx = aead_request_ctx(req);

    rctx->decrypt = true;
    if (!my_gcm_hw_can_handle(req))
        return my_gcm_do_fallback(req, true);

    return crypto_transfer_aead_request_to_engine(ctx->dd->engine, req);
}

static int my_gcm_do_one_request(struct crypto_engine *engine, void *areq)
{
    struct crypto_async_request *base = areq;
    struct aead_request *req = container_of(base, struct aead_request, base);
    struct crypto_aead *tfm = crypto_aead_reqtfm(req);
    struct my_gcm_tfm_ctx *ctx = crypto_aead_ctx(tfm);
    struct my_gcm_reqctx *rctx = aead_request_ctx_dma(req);

    (void)engine;
    ctx->dd->active_req = req;
    memcpy(rctx->iv, req->iv, crypto_aead_ivsize(tfm));

    my_gcm_map_req(ctx->dd, req, rctx);
    my_gcm_program(ctx->dd, req->assoclen, req->cryptlen,
                   crypto_aead_authsize(tfm), rctx->iv, rctx->decrypt);
    my_gcm_kick(ctx->dd);
    return -EINPROGRESS;
}

static irqreturn_t my_gcm_irq(int irq, void *data)
{
    struct my_gcm_dev *dd = data;
    struct aead_request *req = dd->active_req;
    struct crypto_aead *tfm = crypto_aead_reqtfm(req);
    struct my_gcm_reqctx *rctx = aead_request_ctx_dma(req);
    int err = my_gcm_status(dd);

    (void)irq;
    (void)tfm;
    if (!err && rctx->decrypt && !my_gcm_tag_ok(dd))
        err = -EBADMSG;

    if (rctx->mapped)
        my_gcm_unmap_req(dd, req, rctx);

    dd->active_req = NULL;
    crypto_finalize_aead_request(dd->engine, req, err);
    return IRQ_HANDLED;
}

static struct aead_engine_alg my_gcm_alg = {
    .base = {
        .setkey      = my_gcm_setkey,
        .setauthsize = my_gcm_setauthsize,
        .encrypt     = my_gcm_encrypt,
        .decrypt     = my_gcm_decrypt,
        .init        = my_gcm_init_tfm,
        .exit        = my_gcm_exit_tfm,
        .ivsize      = 12,
        .maxauthsize = 16,
        .chunksize   = 1,
        .base = {
            .cra_name        = "gcm(aes)",
            .cra_driver_name = "gcm-aes-myhw",
            .cra_priority    = 450,
            .cra_flags       = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK,
            .cra_blocksize   = 1,
            .cra_ctxsize     = sizeof(struct my_gcm_tfm_ctx),
            .cra_alignmask   = 0xf,
            .cra_module      = THIS_MODULE,
        },
    },
    .op = {
        .do_one_request = my_gcm_do_one_request,
    },
};
구현 항목driver가 반드시 맞춰야 하는 의미
setauthsize()하드웨어가 지원하는 tag 길이만 허용해야 합니다. 허용 범위를 넓게 잡아 두고 내부에서 임의 절단하면 FIPS, IPsec, TLS 상호운용성이 모두 깨집니다.
req->assoclenAAD 길이입니다. AAD는 암호화되지 않지만 tag 계산에는 반드시 포함됩니다. 일부 H/W는 이를 별도 length register로 요구합니다.
req->cryptlen암호화에서는 평문 길이, 복호화에서는 암호문 + tag 길이입니다. decryption 경로에서 tag 길이를 따로 빼지 않으면 DMA length를 잘못 프로그래밍합니다.
-EBADMSG복호화 tag 검증 실패는 일반 I/O 오류가 아니라 반드시 -EBADMSG로 내야 합니다. 상위 계층은 이를 "인증 실패"로 구분합니다.
fallback 동기화fallback tfm에도 같은 key와 authsize가 들어 있어야 합니다. 그렇지 않으면 특정 입력에서만 결과가 달라지는 가장 나쁜 종류의 버그가 생깁니다.
AEAD driver에서 특히 흔한 실수: 하드웨어가 [AAD][payload][tag]를 한 덩어리로 받는다고 해서 커널 요청도 그렇게 단순하다고 생각하면 안 됩니다. srcdst는 동일하거나 다를 수 있고, AAD는 out-of-place에서도 목적지 SG에 "공간은 있지만 실제로는 복사하지 않는" 구간이 될 수 있습니다. 따라서 SG를 단순 linear 버퍼처럼 다루지 말고, 하드웨어가 꼭 contiguous를 요구하면 그때만 bounce buffer로 내리는 편이 안전합니다.

템플릿(Template) 구현

템플릿은 기존 알고리즘을 래핑하여 새로운 알고리즘을 자동 생성하는 메커니즘입니다. 예를 들어 cbc 템플릿은 임의의 블록 암호를 CBC 모드로 래핑합니다:

#include <crypto/internal/skcipher.h>

/* 템플릿 인스턴스의 컨텍스트 */
struct my_tmpl_instance_ctx {
    struct crypto_skcipher_spawn spawn;  /* 내부 알고리즘 참조 */
};

struct my_tmpl_tfm_ctx {
    struct crypto_skcipher *child;      /* 실제 내부 tfm */
};

/* 템플릿 인스턴스 생성: "mymode(aes)" 요청 시 호출 */
static int my_tmpl_create(struct crypto_template *tmpl,
                          struct rtattr **tb)
{
    struct skcipher_instance *inst;
    struct my_tmpl_instance_ctx *ictx;
    struct skcipher_alg_common *alg;
    u32 mask;
    int err;

    /* 내부 알고리즘 이름 추출 (예: "aes" from "mymode(aes)") */
    err = crypto_check_attr_type(tb, CRYPTO_ALG_TYPE_SKCIPHER, &mask);
    if (err)
        return err;

    /* 인스턴스 할당 */
    inst = kzalloc(sizeof(*inst) + sizeof(*ictx), GFP_KERNEL);
    if (!inst)
        return -ENOMEM;

    ictx = skcipher_instance_ctx(inst);

    /* 내부 알고리즘(spawn) 바인딩 */
    err = crypto_grab_skcipher(&ictx->spawn,
                              skcipher_crypto_instance(inst),
                              crypto_attr_alg_name(tb[1]),
                              0, mask);
    if (err)
        goto err_free;

    alg = crypto_spawn_skcipher_alg_common(&ictx->spawn);

    /* 인스턴스 이름 조합: "mymode(inner_alg_name)" */
    err = snprintf(inst->alg.base.cra_name, CRYPTO_MAX_ALG_NAME,
                   "mymode(%s)", alg->base.cra_name);

    err = snprintf(inst->alg.base.cra_driver_name, CRYPTO_MAX_ALG_NAME,
                   "mymode(%s)", alg->base.cra_driver_name);

    /* 내부 알고리즘 속성 상속 */
    inst->alg.base.cra_priority = alg->base.cra_priority;
    inst->alg.base.cra_blocksize = alg->base.cra_blocksize;
    inst->alg.base.cra_alignmask = alg->base.cra_alignmask;
    inst->alg.base.cra_ctxsize = sizeof(struct my_tmpl_tfm_ctx);

    inst->alg.ivsize = alg->ivsize;
    inst->alg.min_keysize = alg->min_keysize;
    inst->alg.max_keysize = alg->max_keysize;

    /* 콜백 등록 */
    inst->alg.setkey  = my_tmpl_setkey;
    inst->alg.encrypt = my_tmpl_encrypt;
    inst->alg.decrypt = my_tmpl_decrypt;
    inst->alg.init    = my_tmpl_init_tfm;
    inst->alg.exit    = my_tmpl_exit_tfm;

    inst->free = my_tmpl_free_instance;

    /* 인스턴스 등록 */
    err = skcipher_register_instance(tmpl, inst);
    if (err)
        goto err_free;

    return 0;

err_free:
    kfree(inst);
    return err;
}

/* 템플릿 등록 */
static struct crypto_template my_tmpl = {
    .name   = "mymode",
    .create = my_tmpl_create,
    .module = THIS_MODULE,
};

static int __init my_tmpl_mod_init(void)
{
    return crypto_register_template(&my_tmpl);
}

static void __exit my_tmpl_mod_exit(void)
{
    crypto_unregister_template(&my_tmpl);
}
ℹ️

Spawn 메커니즘: crypto_spawn은 템플릿이 내부 알고리즘의 생명주기를 추적하도록 합니다. 내부 알고리즘이 해제되면 이를 사용하는 모든 템플릿 인스턴스도 자동으로 무효화(Invalidation)됩니다. 이로써 모듈 unload 시 dangling reference를 방지합니다.

드라이버 작성자의 핵심 감각: tfm 컨텍스트와 request 컨텍스트를 분리하라

Crypto API 제공자 입장에서 가장 중요한 설계는 무엇이 tfm에 속하고 무엇이 request에 속하는가입니다. 같은 알고리즘 인스턴스를 여러 CPU가 동시에 사용할 수 있으므로, cra_ctxsize로 잡은 tfm 컨텍스트에는 키 스케줄, 디바이스 포인터, fallback tfm, 정적 capability 같은 장수 상태만 두어야 합니다. 반대로 DMA mapping, bounce buffer, SG walk 상태, IRQ 완료용 descriptor는 요청마다 독립적이어야 하므로 reqsize에 들어가야 합니다.

#include <crypto/internal/skcipher.h>
#include <crypto/internal/hash.h>

struct my_dev {
    struct device *dev;
    void __iomem *regs;
    int irq;
};

/* tfm당 한 번만 살아 있는 상태 */
struct my_skcipher_tfm_ctx {
    struct my_dev *dd;
    struct crypto_skcipher *fallback;
    u32 round_keys[60];
    unsigned int keylen;
};

/* 요청마다 새로 필요한 상태 */
struct my_skcipher_reqctx {
    dma_addr_t src_dma;
    dma_addr_t dst_dma;
    u8 iv[16];
    bool mapped;
    bool use_fallback;
};

static int my_skcipher_init_tfm(struct crypto_skcipher *tfm)
{
    struct my_skcipher_tfm_ctx *ctx = crypto_skcipher_ctx(tfm);

    ctx->fallback = crypto_alloc_skcipher("cbc(aes-generic)", 0, 0);
    if (IS_ERR(ctx->fallback))
        return PTR_ERR(ctx->fallback);

    /* DMA 정렬까지 고려한 request private area */
    crypto_skcipher_set_reqsize_dma(tfm, sizeof(struct my_skcipher_reqctx));
    return 0;
}

static void my_skcipher_exit_tfm(struct crypto_skcipher *tfm)
{
    struct my_skcipher_tfm_ctx *ctx = crypto_skcipher_ctx(tfm);
    crypto_free_skcipher(ctx->fallback);
}

static int my_ahash_init_tfm(struct crypto_ahash *tfm)
{
    /* 해시도 동일한 패턴: statesize와 reqsize를 정직하게 설정 */
    crypto_ahash_set_statesize(tfm, 32);
    crypto_ahash_set_reqsize_dma(tfm, 128);
    return 0;
}
저장 위치무엇을 넣어야 하나무엇을 넣으면 안 되나
cra_ctxsize로 확보한 tfm ctx키 스케줄, fallback tfm, 디바이스 핸들, capability, queue 선택 정보진행 중 DMA 주소, 현재 요청 포인터, 이번 요청용 IV/nonce
reqsize로 확보한 request ctxDMA mapping, descriptor, bounce buffer, 완료 상태, per-request IV 복사본공유 키, 장수 전역 상태, 다른 요청과 공유되는 포인터
statesize (해시)export/import 가능한 중간 hash 상태하드웨어 전용 임시 메타데이터 전체를 그대로 덤프(Dump)하는 것
실전 규칙: 같은 tfm으로 두 request가 동시에 날아갈 수 있다는 전제를 버리면 안 됩니다. 특히 ahash header 문서가 강조하듯 update()는 같은 transformation object에 대해 병렬 호출될 수 있으므로, tfm ctx를 "현재 진행 중 해시 상태"처럼 사용하면 레이스가 납니다.

드라이버 메타데이터를 대충 정하면 생기는 문제

Crypto driver는 단순히 함수 포인터만 채우는 구조가 아닙니다. blocksize, alignmask, chunksize, walksize, reqsize 같은 메타데이터가 상위 계층의 메모리 배치, bounce buffer 사용, page 경계 처리, zero-copy 가능 여부를 결정합니다.

필드드라이버 작성 기준잘못 잡으면 생기는 일
cra_blocksize수학적/프로토콜적 처리 단위입니다. DMA burst 크기가 아닙니다.상위 계층이 잘못된 길이 제약을 가정해 부분 블록 처리에서 망가집니다.
cra_alignmask하드웨어가 실제로 요구하는 정렬을 솔직하게 적어야 합니다.너무 낮게 적으면 하드웨어 fault, 너무 높게 적으면 불필요한 realign과 복사가 증가합니다.
chunksize알고리즘이 의미 있게 처리할 수 있는 최소 chunk 단위입니다.skcipher walk가 비효율적으로 쪼개지거나, page 경계에서 fallback이 과도하게 발생합니다.
walksizeskcipher_walk에 적합한 처리 크기입니다. 디바이스가 한 번에 소비하기 좋은 단위와 맞추는 경우가 많습니다.작게 잡으면 loop overhead가 커지고, 너무 크게 잡으면 bounce/re-align 확률이 올라갑니다.
reqsize요청당 필요한 descriptor, DMA 주소, 임시 상태만 넣습니다.부족하면 overflow, 과하면 요청 객체가 비대해지고 캐시 locality가 나빠집니다.
CRYPTO_ALG_ALLOCATES_MEMORY요청 처리 중 메모리 할당이 실제로 일어나면 반드시 반영해야 합니다.할당이 없다고 믿은 상위 계층이 atomic 경로에서 알고리즘을 사용하다가 경고나 실패를 맞습니다.
정렬 관련 핵심: include/linux/crypto.halignmask와 page 경계 조건을 만족하지 못하면 Crypto API가 내부 realign/임시 버퍼를 만들 수 있음을 명시합니다. 따라서 "왜 H/W driver가 빠르지 않지?"라는 문제는 종종 알고리즘 수학이 아니라 메타데이터 부정확성에서 시작합니다.

ahash 드라이버 구현: DMA 해시 엔진 + 소프트웨어 fallback

해시 가속기는 실무에서 가장 다양한 제약을 가집니다. 어떤 엔진은 진짜 스트리밍(init/update/final)을 지원하고, 어떤 엔진은 digest one-shot만 빠르며, 어떤 엔진은 finup는 되지만 중간 상태 export/import는 안 됩니다. 따라서 ahash driver는 하드웨어가 지원하는 가장 강한 연산을 기준으로 구현하고, 그보다 풍부한 API 표면은 소프트웨어 fallback으로 메워야 합니다.

#include <crypto/internal/hash.h>
#include <crypto/engine.h>
#include <linux/dma-mapping.h>

struct my_sha_dev {
    struct device *dev;
    struct crypto_engine *engine;
    struct ahash_request *active_req;
};

struct my_sha_tfm_ctx {
    struct my_sha_dev *dd;
    struct crypto_shash *fallback;
};

struct my_sha_reqctx {
    dma_addr_t src_dma;
    dma_addr_t result_dma;
    u8 digest[32];
    bool mapped;
};

static int my_sha_init_tfm(struct crypto_ahash *tfm)
{
    struct my_sha_tfm_ctx *ctx = crypto_ahash_ctx(tfm);

    ctx->fallback = crypto_alloc_shash("sha256-generic", 0, 0);
    if (IS_ERR(ctx->fallback))
        return PTR_ERR(ctx->fallback);

    crypto_ahash_set_statesize(tfm, 32);
    crypto_ahash_set_reqsize_dma(tfm, sizeof(struct my_sha_reqctx));
    return 0;
}

static void my_sha_exit_tfm(struct crypto_ahash *tfm)
{
    struct my_sha_tfm_ctx *ctx = crypto_ahash_ctx(tfm);
    crypto_free_shash(ctx->fallback);
}

static int my_sha_init(struct ahash_request *req)
{
    struct my_sha_reqctx *rctx = ahash_request_ctx(req);
    memset(rctx, 0, sizeof(*rctx));
    return 0;
}

static int my_sha_fallback_digest(struct ahash_request *req)
{
    struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
    struct my_sha_tfm_ctx *ctx = crypto_ahash_ctx(tfm);
    SHASH_DESC_ON_STACK(desc, ctx->fallback);

    desc->tfm = ctx->fallback;
    return shash_ahash_digest(req, desc);
}

static int my_sha_digest(struct ahash_request *req)
{
    struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
    struct my_sha_tfm_ctx *ctx = crypto_ahash_ctx(tfm);

    /* 하드웨어가 요구하는 SG 조건을 만족하지 않으면 fallback */
    if (!my_sha_hw_can_digest(req->src, req->nbytes))
        return my_sha_fallback_digest(req);

    return crypto_transfer_hash_request_to_engine(ctx->dd->engine, req);
}

static int my_sha_update(struct ahash_request *req)
{
    /* 이 예시는 one-shot 하드웨어를 가정하므로 진짜 스트리밍은 fallback 처리 */
    return my_sha_fallback_digest(req);
}

static int my_sha_final(struct ahash_request *req)
{
    return my_sha_fallback_digest(req);
}

static int my_sha_do_one_request(struct crypto_engine *engine, void *areq)
{
    struct crypto_async_request *base = areq;
    struct ahash_request *req = ahash_request_cast(base);
    struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
    struct my_sha_tfm_ctx *ctx = crypto_ahash_ctx(tfm);
    struct my_sha_reqctx *rctx = ahash_request_ctx_dma(req);

    (void)engine;
    ctx->dd->active_req = req;

    rctx->src_dma = dma_map_sgtable(ctx->dd->dev, my_req_to_sgt(req), DMA_TO_DEVICE, 0);
    rctx->result_dma = dma_map_single(ctx->dd->dev, rctx->digest, 32, DMA_FROM_DEVICE);
    rctx->mapped = true;

    /* 레지스터 적재 후 SHA 엔진 시작 */
    my_sha_hw_kick(ctx->dd, req, rctx);
    return -EINPROGRESS;
}

static irqreturn_t my_sha_irq(int irq, void *data)
{
    struct my_sha_dev *dd = data;
    struct ahash_request *req = dd->active_req;
    struct my_sha_reqctx *rctx = ahash_request_ctx_dma(req);
    int err = my_sha_hw_status(dd);

    if (!err)
        memcpy(req->result, rctx->digest, 32);

    if (rctx->mapped)
        my_sha_unmap_req(dd, req, rctx);

    dd->active_req = NULL;
    crypto_finalize_hash_request(dd->engine, req, err);
    return IRQ_HANDLED;
}

static struct ahash_engine_alg my_sha_alg = {
    .base = {
        .init     = my_sha_init,
        .update   = my_sha_update,
        .final    = my_sha_final,
        .digest   = my_sha_digest,
        .init_tfm = my_sha_init_tfm,
        .exit_tfm = my_sha_exit_tfm,
        .reqsize  = sizeof(struct my_sha_reqctx),
        .halg = {
            .digestsize = 32,
            .statesize  = 32,
            .base = {
                .cra_name        = "sha256",
                .cra_driver_name = "sha256-myhw",
                .cra_priority    = 300,
                .cra_flags       = CRYPTO_ALG_ASYNC | CRYPTO_ALG_KERN_DRIVER_ONLY,
                .cra_blocksize   = 64,
                .cra_ctxsize     = sizeof(struct my_sha_tfm_ctx),
                .cra_alignmask   = 0,
                .cra_module      = THIS_MODULE,
            },
        },
    },
    .op = {
        .do_one_request = my_sha_do_one_request,
    },
};
하드웨어 능력드라이버 구현 전략
진짜 스트리밍 가능init/update/final/export/import를 하드웨어 상태 기계에 맞게 직접 구현합니다.
finup까지만 가능update()는 reqctx에 staging하고, 마지막 final() 또는 finup()에서 실제 장치에 태웁니다.
one-shot digest만 빠름digest()는 H/W, 나머지는 shash fallback으로 보내는 혼합형이 가장 현실적입니다.
ahash에서 특히 중요한 계약: header 문서가 명시하듯 update()는 같은 tfm에 대해 병렬 호출될 수 있으므로 "현재 해시 상태"를 tfm ctx에 저장하면 안 됩니다. 중간 상태는 request ctx나 export/import 버퍼에 있어야 합니다.

RNG 드라이버 구현: TRNG와 DRBG 래퍼를 구분하라

rng_alg는 단순해 보이지만 정책적 의미가 큽니다. 하드웨어 TRNG를 그대로 노출하는지, 내부적으로 DRBG를 돌리는지, 외부에서 seed를 넣어야 하는지에 따라 seed()seedsize 의미가 달라집니다. 특히 FIPS 경계 안에서는 "엔트로피를 내는 장치"와 "결정론적으로 바이트를 내는 DRBG"를 혼동하면 안 됩니다.

#include <crypto/rng.h>
#include <crypto/internal/rng.h>

struct my_trng_ctx {
    struct my_dev *dd;
    bool seeded;
    u8 personalization[48];
};

static inline struct my_trng_ctx *my_rng_ctx(struct crypto_rng *tfm)
{
    return crypto_tfm_ctx(crypto_rng_tfm(tfm));
}

static int my_trng_seed(struct crypto_rng *tfm,
                        const u8 *seed, unsigned int slen)
{
    struct my_trng_ctx *ctx = my_rng_ctx(tfm);

    if (slen != 48)
        return -EINVAL;

    memcpy(ctx->personalization, seed, slen);
    ctx->seeded = true;
    return 0;
}

static int my_trng_generate(struct crypto_rng *tfm,
                            const u8 *src, unsigned int slen,
                            u8 *dst, unsigned int dlen)
{
    struct my_trng_ctx *ctx = my_rng_ctx(tfm);

    if (!ctx->seeded)
        return -EINVAL;

    while (dlen) {
        u32 word;

        if (!my_hw_rng_ready(ctx->dd))
            return -EAGAIN;

        word = my_hw_rng_read(ctx->dd);
        memcpy(dst, &word, min(dlen, sizeof(word)));
        dst += min(dlen, sizeof(word));
        dlen -= min(dlen, sizeof(word));
    }

    if (src && slen)
        my_hw_mix_additional_input(ctx->dd, src, slen);

    return 0;
}

static struct rng_alg my_trng_alg = {
    .generate = my_trng_generate,
    .seed     = my_trng_seed,
    .seedsize = 48,
    .base = {
        .cra_name        = "mytrng",
        .cra_driver_name = "mytrng-hw",
        .cra_priority    = 200,
        .cra_ctxsize     = sizeof(struct my_trng_ctx),
        .cra_module      = THIS_MODULE,
    },
};
유형seedsize 설계seed() 의미
하드웨어 TRNG보통 0 또는 정책상 필요한 personalization 길이대개 optional personalization 또는 내부 mixing 트리거
H/W + DRBG 하이브리드DRBG가 요구하는 seed 길이DRBG 상태 재초기화
순수 DRBG 구현필수 seed 길이를 정확히 명시새 내부 상태를 만드는 핵심 콜백
RNG 드라이버의 흔한 실수: self-seeding 하드웨어인데도 seedsize를 애매하게 잡거나, 반대로 외부 시드가 꼭 필요한데 seedsize = 0으로 등록하는 경우입니다. 이 값은 소비자가 crypto_rng_reset()을 언제 불러야 하는지 결정하므로, 정책적으로 정확해야 합니다.

압축 드라이버 구현: acomp는 engine helper가 없다는 점이 핵심

acomp는 비동기 압축 API이지만, skcipher/aead/ahash처럼 crypto_engine 래퍼가 제공되지는 않습니다. 따라서 비동기 압축 드라이버는 자체 queue/workqueue/IRQ 경로를 직접 관리하는 경우가 많습니다. 이것이 암호 드라이버와 압축 드라이버의 큰 차이점입니다.

#include <crypto/internal/acompress.h>
#include <linux/workqueue.h>

struct my_zip_tfm_ctx {
    struct my_dev *dd;
};

struct my_zip_reqctx {
    struct work_struct work;
    struct acomp_req *req;
    bool allocated_dst;
};

static void my_zip_dst_free(struct scatterlist *dst)
{
    my_sg_free(dst);
}

static void my_zip_do_work(struct work_struct *work)
{
    struct my_zip_reqctx *rctx = container_of(work, struct my_zip_reqctx, work);
    struct acomp_req *req = rctx->req;
    int err;

    err = my_zip_hw_run(req);
    if (!err)
        req->dlen = my_zip_result_len(req);

    acomp_request_complete(req, err);
}

static int my_zip_compress(struct acomp_req *req)
{
    struct crypto_acomp *tfm = crypto_acomp_reqtfm(req);
    struct my_zip_tfm_ctx *ctx = acomp_tfm_ctx(tfm);
    struct my_zip_reqctx *rctx = acomp_request_ctx(req);

    memset(rctx, 0, sizeof(*rctx));
    rctx->req = req;

    if (req->flags & CRYPTO_ACOMP_ALLOC_OUTPUT) {
        req->dst = my_zip_alloc_output_sg(req->dlen);
        if (!req->dst)
            return -ENOMEM;
        rctx->allocated_dst = true;
    }

    if (!my_zip_hw_can_handle(ctx->dd, req))
        return -EINVAL;

    INIT_WORK(&rctx->work, my_zip_do_work);
    queue_work(ctx->dd->wq, &rctx->work);
    return -EINPROGRESS;
}

static int my_zip_decompress(struct acomp_req *req)
{
    /* 압축과 동일한 패턴으로 하드웨어 또는 workqueue 경로 사용 */
    return my_zip_compress(req);
}

static int my_zip_init_tfm(struct crypto_acomp *tfm)
{
    struct my_zip_tfm_ctx *ctx = acomp_tfm_ctx(tfm);
    ctx->dd = my_pick_zip_device();
    return 0;
}

static struct acomp_alg my_zip_alg = {
    .compress = my_zip_compress,
    .decompress = my_zip_decompress,
    .dst_free = my_zip_dst_free,
    .init = my_zip_init_tfm,
    .reqsize = sizeof(struct my_zip_reqctx),
    .base = {
        .cra_name        = "deflate",
        .cra_driver_name = "deflate-myzip",
        .cra_priority    = 250,
        .cra_flags       = CRYPTO_ALG_ASYNC,
        .cra_ctxsize     = sizeof(struct my_zip_tfm_ctx),
        .cra_module      = THIS_MODULE,
    },
};
포인트설명
req->dst == NULL소비자가 출력 버퍼 자동 할당을 요청한 경우입니다. 드라이버가 SG를 할당했다면 dst_free로 정리 경로를 제공해야 합니다.
acomp_request_complete()비동기 완료 통지는 이 헬퍼로 수렴시키는 편이 안전합니다.
engine helper 부재압축은 crypto_engine_register_*() 계열이 없으므로 자체 queue 설계가 필요합니다.

AKCIPHER 드라이버 구현: 길이 계약과 키 파싱이 절반이다

공개키 엔진 드라이버는 수학 연산보다도 키 파싱, 출력 길이 산정, verify 입력 규약을 정확히 맞추는 일이 더 어렵습니다. 특히 akcipher_request는 검증 경로에서 src = signature || digest 형태를 사용하므로, src_lendst_len의 의미가 연산마다 달라진다는 점을 기억해야 합니다.

#include <crypto/akcipher.h>
#include <crypto/internal/akcipher.h>

struct my_rsa_tfm_ctx {
    struct my_dev *dd;
    unsigned int modulus_size;
    bool has_pub;
    bool has_priv;
};

struct my_rsa_reqctx {
    dma_addr_t src_dma;
    dma_addr_t dst_dma;
};

static int my_rsa_set_pub_key(struct crypto_akcipher *tfm,
                              const void *key, unsigned int keylen)
{
    struct my_rsa_tfm_ctx *ctx = akcipher_tfm_ctx(tfm);

    /* BER/DER 파싱 후 modulus 길이 계산 */
    ctx->modulus_size = my_parse_rsa_pubkey(key, keylen, ctx);
    if (!ctx->modulus_size)
        return -EINVAL;

    ctx->has_pub = true;
    return 0;
}

static int my_rsa_set_priv_key(struct crypto_akcipher *tfm,
                               const void *key, unsigned int keylen)
{
    struct my_rsa_tfm_ctx *ctx = akcipher_tfm_ctx(tfm);

    ctx->modulus_size = my_parse_rsa_privkey(key, keylen, ctx);
    if (!ctx->modulus_size)
        return -EINVAL;

    ctx->has_priv = true;
    return 0;
}

static unsigned int my_rsa_max_size(struct crypto_akcipher *tfm)
{
    struct my_rsa_tfm_ctx *ctx = akcipher_tfm_ctx(tfm);
    return ctx->modulus_size;
}

static int my_rsa_encrypt(struct akcipher_request *req)
{
    struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req);
    struct my_rsa_tfm_ctx *ctx = akcipher_tfm_ctx(tfm);

    if (!ctx->has_pub)
        return -EINVAL;

    if (req->dst_len < ctx->modulus_size) {
        req->dst_len = ctx->modulus_size;
        return -EOVERFLOW;
    }

    /* PKCS#1/OAEP padding 검증 후 장치 또는 소프트웨어 경로 수행 */
    req->dst_len = ctx->modulus_size;
    return my_rsa_hw_encrypt(req, ctx);
}

static int my_rsa_verify(struct akcipher_request *req)
{
    struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req);
    struct my_rsa_tfm_ctx *ctx = akcipher_tfm_ctx(tfm);

    /* verify 입력 규약:
     * req->src     = [signature][digest]
     * req->src_len = signature 길이
     * req->dst_len = digest 길이
     */
    if (req->src_len != ctx->modulus_size)
        return -EINVAL;

    if (!my_rsa_sig_matches_digest(req, ctx))
        return -EBADMSG;

    return 0;
}

static int my_rsa_init_tfm(struct crypto_akcipher *tfm)
{
    akcipher_set_reqsize_dma(tfm, sizeof(struct my_rsa_reqctx));
    return 0;
}

static struct akcipher_alg my_rsa_alg = {
    .encrypt      = my_rsa_encrypt,
    .verify       = my_rsa_verify,
    .set_pub_key  = my_rsa_set_pub_key,
    .set_priv_key = my_rsa_set_priv_key,
    .max_size     = my_rsa_max_size,
    .init         = my_rsa_init_tfm,
    .base = {
        .cra_name        = "rsa",
        .cra_driver_name = "rsa-myhw",
        .cra_priority    = 500,
        .cra_flags       = CRYPTO_ALG_ASYNC,
        .cra_ctxsize     = sizeof(struct my_rsa_tfm_ctx),
        .cra_module      = THIS_MODULE,
    },
};
계약드라이버가 반드시 지킬 것
max_size()현재 설정된 키 기준으로 충분한 출력 길이를 반환해야 합니다.
출력 버퍼 부족req->dst_len을 필요한 길이로 갱신하고 오류를 반환해야 합니다.
verify 입력 형식src = signature || digest, src_len = signature, dst_len = digest 규약을 지켜야 합니다.
키 설정 실패부분적으로 파싱한 키 상태를 남기지 말고, 다음 setkey 호출에서 일관되게 다시 시작해야 합니다.

KPP 드라이버 구현: 공개키 생성과 공유 비밀 계산을 분리하라

KPP 드라이버는 "세션 키 합의"라는 높은 수준 개념 대신, 훨씬 더 낮은 두 primitive만 제공합니다. 하나는 내 공개키 생성, 다른 하나는 상대 공개키를 입력으로 공유 비밀 계산입니다. 소비자(TLS, IPsec, WireGuard 전단 등)는 그 결과를 다시 KDF에 넣어 실제 데이터 키를 만듭니다.

#include <crypto/kpp.h>
#include <crypto/internal/kpp.h>
#include <crypto/ecdh.h>

struct my_ecdh_tfm_ctx {
    struct my_dev *dd;
    u8 priv[80];
    unsigned int priv_len;
    unsigned int pub_len;
    unsigned int secret_len;
};

struct my_ecdh_reqctx {
    dma_addr_t src_dma;
    dma_addr_t dst_dma;
};

static int my_ecdh_set_secret(struct crypto_kpp *tfm,
                               const void *buffer, unsigned int len)
{
    struct my_ecdh_tfm_ctx *ctx = kpp_tfm_ctx(tfm);
    struct ecdh params;
    int ret;

    ret = crypto_ecdh_decode_key(buffer, len, &params);
    if (ret)
        return ret;

    if (params.key_size > sizeof(ctx->priv))
        return -EINVAL;

    memcpy(ctx->priv, params.key, params.key_size);
    ctx->priv_len = params.key_size;
    ctx->pub_len = my_ecdh_public_size(params.key_size);
    ctx->secret_len = my_ecdh_secret_size(params.key_size);
    return 0;
}

static unsigned int my_ecdh_max_size(struct crypto_kpp *tfm)
{
    struct my_ecdh_tfm_ctx *ctx = kpp_tfm_ctx(tfm);
    return max(ctx->pub_len, ctx->secret_len);
}

static int my_ecdh_generate_public_key(struct kpp_request *req)
{
    struct crypto_kpp *tfm = crypto_kpp_reqtfm(req);
    struct my_ecdh_tfm_ctx *ctx = kpp_tfm_ctx(tfm);

    if (req->dst_len < ctx->pub_len) {
        req->dst_len = ctx->pub_len;
        return -EOVERFLOW;
    }

    req->dst_len = ctx->pub_len;
    return my_ecdh_hw_make_public(req, ctx);
}

static int my_ecdh_compute_shared_secret(struct kpp_request *req)
{
    struct crypto_kpp *tfm = crypto_kpp_reqtfm(req);
    struct my_ecdh_tfm_ctx *ctx = kpp_tfm_ctx(tfm);

    if (req->dst_len < ctx->secret_len) {
        req->dst_len = ctx->secret_len;
        return -EOVERFLOW;
    }

    req->dst_len = ctx->secret_len;
    return my_ecdh_hw_shared_secret(req, ctx);
}

static int my_ecdh_init_tfm(struct crypto_kpp *tfm)
{
    kpp_set_reqsize_dma(tfm, sizeof(struct my_ecdh_reqctx));
    return 0;
}

static struct kpp_alg my_ecdh_alg = {
    .set_secret            = my_ecdh_set_secret,
    .generate_public_key   = my_ecdh_generate_public_key,
    .compute_shared_secret = my_ecdh_compute_shared_secret,
    .max_size              = my_ecdh_max_size,
    .init                  = my_ecdh_init_tfm,
    .base = {
        .cra_name        = "ecdh",
        .cra_driver_name = "ecdh-myhw",
        .cra_priority    = 350,
        .cra_flags       = CRYPTO_ALG_ASYNC,
        .cra_ctxsize     = sizeof(struct my_ecdh_tfm_ctx),
        .cra_module      = THIS_MODULE,
    },
};
KPP 구현 포인트: packed secret 형식은 crypto_ecdh_encode_key() / crypto_ecdh_decode_key() 같은 helper를 활용하는 편이 안전합니다. 드라이버가 독자 형식을 만들기 시작하면 소비자와의 호환성, test vector, FIPS 경계 설명이 모두 어려워집니다.

하드웨어 드라이버 등록(Driver Registration) 패턴: crypto_engine을 기준으로 보는 전체 수명

하드웨어 crypto driver의 전형적인 뼈대는 다음 순서입니다. probe에서 engine 생성 → 알고리즘 등록 → tfm init에서 per-transform state 준비 → encrypt/decrypt/digest가 engine queue에 요청 위임 → IRQ에서 finalize 입니다. 이 패턴을 지키면 backlog와 완료 통지가 Crypto API 규약에 맞춰 정리됩니다.

#include <crypto/engine.h>
#include <crypto/internal/skcipher.h>

struct my_accel_dev {
    struct device *dev;
    struct crypto_engine *engine;
};

static int my_hw_encrypt(struct skcipher_request *req)
{
    struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
    struct my_skcipher_tfm_ctx *ctx = crypto_skcipher_ctx(tfm);
    return crypto_transfer_skcipher_request_to_engine(ctx->dd->engine, req);
}

static int my_hw_decrypt(struct skcipher_request *req)
{
    return my_hw_encrypt(req);
}

static int my_hw_do_one_request(struct crypto_engine *engine, void *areq)
{
    struct crypto_async_request *base = areq;
    struct skcipher_request *req = container_of(base, struct skcipher_request, base);

    (void)engine;
    my_hw_map_req(req);
    my_hw_start(req);
    return -EINPROGRESS;
}

static struct skcipher_engine_alg my_hw_algs[] = {{
    .base = {
        .setkey      = my_cipher_setkey,
        .encrypt     = my_hw_encrypt,
        .decrypt     = my_hw_decrypt,
        .init        = my_skcipher_init_tfm,
        .exit        = my_skcipher_exit_tfm,
        .min_keysize = 16,
        .max_keysize = 32,
        .ivsize      = 16,
        .chunksize   = 16,
        .walksize    = 16,
        .base = {
            .cra_name        = "cbc(aes)",
            .cra_driver_name = "cbc-aes-myhw",
            .cra_priority    = 400,
            .cra_flags       = CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK,
            .cra_blocksize   = 16,
            .cra_ctxsize     = sizeof(struct my_skcipher_tfm_ctx),
            .cra_alignmask   = 0xf,
            .cra_module      = THIS_MODULE,
        },
    },
    .op = {
        .do_one_request = my_hw_do_one_request,
    },
}};

static int my_probe(struct platform_device *pdev)
{
    struct my_accel_dev *dd;
    int err;

    dd = devm_kzalloc(&pdev->dev, sizeof(*dd), GFP_KERNEL);
    if (!dd)
        return -ENOMEM;

    dd->dev = &pdev->dev;
    dd->engine = crypto_engine_alloc_init(&pdev->dev, true);
    if (!dd->engine)
        return -ENOMEM;

    err = crypto_engine_start(dd->engine);
    if (err)
        goto err_engine_exit;

    err = crypto_engine_register_skciphers(my_hw_algs, ARRAY_SIZE(my_hw_algs));
    if (err)
        goto err_engine_stop;

    return 0;

err_engine_stop:
    crypto_engine_stop(dd->engine);
err_engine_exit:
    crypto_engine_exit(dd->engine);
    return err;
}

static void my_remove(struct platform_device *pdev)
{
    struct my_accel_dev *dd = platform_get_drvdata(pdev);

    crypto_engine_unregister_skciphers(my_hw_algs, ARRAY_SIZE(my_hw_algs));
    crypto_engine_stop(dd->engine);
    crypto_engine_exit(dd->engine);
}
엔진 래퍼지원 타입완료 헬퍼
crypto_engine_register_skcipher*대칭 암호crypto_finalize_skcipher_request()
crypto_engine_register_aead*AEADcrypto_finalize_aead_request()
crypto_engine_register_ahash*해시crypto_finalize_hash_request()
crypto_engine_register_akcipher()공개키crypto_finalize_akcipher_request()
crypto_engine_register_kpp()키 합의crypto_finalize_kpp_request()
순서가 중요: remove 경로에서는 먼저 알고리즘 unregister, 그 다음 engine stop/exit 순으로 가는 편이 안전합니다. 아직 접근 가능한 cra_name이 남아 있는 상태에서 engine을 먼저 없애면, 새 tfm 할당 또는 in-flight 요청과 경합할 수 있습니다.

crypto_queue를 직접 쓰는 드라이버: crypto_engine이 맞지 않을 때

crypto_engine은 대부분의 단일 큐 장치에서 최선의 출발점입니다. 하지만 여러 채널을 가진 장치, vendor scheduler가 별도로 있는 장치, 요청을 일괄(batch)로 묶어야 하는 장치에서는 오히려 crypto_queue를 직접 다루는 편이 더 명확할 수 있습니다. 이 경우 핵심은 단 세 가지입니다. (1) enqueue는 락 안에서, (2) backlog 통지는 dequeue 직후, (3) 현재 active request 포인터는 완료 전에만 유효해야 합니다.

#include <crypto/algapi.h>
#include <crypto/internal/hash.h>

struct my_queue_dev {
    struct device *dev;
    spinlock_t lock;
    struct crypto_queue queue;
    struct ahash_request *active_req;
    bool busy;
};

static int my_queue_digest(struct ahash_request *req)
{
    struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
    struct my_queue_tfm_ctx *ctx = crypto_ahash_ctx(tfm);
    struct my_queue_dev *dd = ctx->dd;
    struct crypto_async_request *async_req, *backlog;
    unsigned long flags;
    int ret = 0;

    spin_lock_irqsave(&dd->lock, flags);

    ret = ahash_enqueue_request(&dd->queue, req);
    if (dd->busy) {
        spin_unlock_irqrestore(&dd->lock, flags);
        return ret;
    }

    backlog = crypto_get_backlog(&dd->queue);
    async_req = crypto_dequeue_request(&dd->queue);
    if (async_req) {
        dd->busy = true;
        dd->active_req = ahash_request_cast(async_req);
    }

    spin_unlock_irqrestore(&dd->lock, flags);

    if (!async_req)
        return ret;

    if (backlog)
        crypto_request_complete(backlog, -EINPROGRESS);

    ret = my_queue_start_hash(dd, dd->active_req);
    if (ret != -EINPROGRESS)
        my_queue_finish_one(dd, ret);
    return ret ? ret : -EINPROGRESS;
}

static void my_queue_finish_one(struct my_queue_dev *dd, int err)
{
    struct crypto_async_request *async_req, *backlog;
    struct ahash_request *req;
    unsigned long flags;

    spin_lock_irqsave(&dd->lock, flags);
    req = dd->active_req;
    dd->active_req = NULL;
    dd->busy = false;

    backlog = crypto_get_backlog(&dd->queue);
    async_req = crypto_dequeue_request(&dd->queue);
    if (async_req) {
        dd->busy = true;
        dd->active_req = ahash_request_cast(async_req);
    }
    spin_unlock_irqrestore(&dd->lock, flags);

    if (req)
        ahash_request_complete(req, err);

    if (backlog)
        crypto_request_complete(backlog, -EINPROGRESS);

    if (dd->active_req)
        my_queue_start_hash(dd, dd->active_req);
}

static irqreturn_t my_queue_irq(int irq, void *data)
{
    struct my_queue_dev *dd = data;
    int err = my_queue_hw_status(dd);

    (void)irq;
    my_queue_finish_one(dd, err);
    return IRQ_HANDLED;
}

static int my_queue_probe(struct platform_device *pdev)
{
    struct my_queue_dev *dd = platform_get_drvdata(pdev);

    spin_lock_init(&dd->lock);
    crypto_init_queue(&dd->queue, 64);
    dd->active_req = NULL;
    dd->busy = false;
    return 0;
}
지점왜 중요한가
ahash_enqueue_request() 반환값0이면 바로 처리 또는 정상 큐잉, -EBUSY면 backlog로 들어간 뒤 나중에 완료될 수 있습니다. "오류"처럼 보여도 실제로는 수락된 요청일 수 있습니다.
crypto_get_backlog()backlog request에게는 -EINPROGRESS 통지를 한 번 보내서 "대기열에 들어갔다"는 사실을 알립니다. 이 의미를 지키지 않으면 상위 계층 대기 로직이 꼬입니다.
active_req 수명IRQ 완료 전까지만 유효합니다. 완료 후 즉시 NULL로 내리고 새 dequeue를 해야 중복 완료/UAF를 막을 수 있습니다.
락 범위queue 조작과 busy 플래그 갱신은 같은 락으로 묶어야 합니다. 반대로 실제 하드웨어 시작 함수는 가능하면 락 밖에서 수행해 인터럽트(Interrupt) 지연을 줄입니다.
선택 기준: 단일 큐 장치라면 먼저 crypto_engine으로 구현하고, 실제로 (1) 다중 채널 스케줄링, (2) 하드웨어 batch submit, (3) vendor firmware queue와의 1:1 매핑 같은 이유가 있을 때만 수동 queue로 내려가는 편이 유지보수에 유리합니다. 직접 queue를 짤수록 completion 중복, backlog 누락, remove 경로 경합 같은 버그가 늘어납니다.

비동기 드라이버의 반환값 계약

Crypto driver에서 가장 치명적인 버그 중 하나는 반환값과 완료 콜백의 의미를 뒤섞는 것입니다. 호출자 입장에서 "이미 끝났는지", "나중에 콜백이 올지", "버퍼 크기만 다시 잡으면 되는지"를 반환값 하나로 판단하기 때문입니다.

반환값의미드라이버가 해야 할 일
0지금 이 함수 호출 안에서 완료됨추가 completion 호출을 해서는 안 됩니다.
-EINPROGRESS요청을 수락했고 나중에 완료됨반드시 IRQ/workqueue/bottom-half 등에서 *_request_complete() 또는 crypto_finalize_*를 호출해야 합니다.
-EBUSYbacklog 처리로 나중에 완료될 수 있음queue/backlog 의미를 지키고, 결국 완료 통지를 해야 합니다.
-EOVERFLOW출력 버퍼가 작음req->dst_len을 필요한 크기로 갱신합니다.
-EINVAL길이, 키, IV, SG 형식, unsupported parameter 오류하드웨어 kick 전에 즉시 실패시키는 편이 좋습니다.
-EBADMSG인증 태그 또는 서명 검증 실패데이터 무결성 실패를 일반 I/O 오류와 구분해 주는 것이 중요합니다.
기억할 규칙 두 개: 첫째, 0을 반환했으면 나중에 callback을 다시 호출하지 마세요. 둘째, -EINPROGRESS 또는 -EBUSY를 반환했으면 언젠가 반드시 완료 통지가 와야 합니다. 이 둘만 어겨도 상위 계층은 중복 완료, 영원한 대기, UAF 같은 치명적 오류를 겪습니다.

테스트 프레임워크

새 알고리즘을 추가하면 반드시 테스트 벡터를 함께 제공해야 합니다. 커널의 Crypto Framework (Crypto API)는 알고리즘 등록 시 자동으로 자가 테스트를 수행합니다.

testmgr — 알고리즘 자동 검증

crypto/testmgr.c는 등록된 모든 알고리즘에 대해 Known Answer Test(KAT)를 수행합니다. 알고리즘이 crypto_register_*()로 등록되면 cryptomgr가 비동기적으로 테스트를 트리거합니다:

/* crypto/testmgr.c — 테스트 벡터 구조체 */

/* 해시 테스트 벡터 */
struct hash_testvec {
    const char *key;          /* HMAC 키 (선택) */
    const char *plaintext;    /* 입력 데이터 */
    const char *digest;       /* 기대 해시 값 */
    unsigned int psize;       /* 입력 길이 */
    unsigned int ksize;       /* 키 길이 */
};

/* 대칭 암호 테스트 벡터 */
struct cipher_testvec {
    const char *key;          /* 암호 키 */
    const char *iv;           /* 초기화 벡터 */
    const char *ptext;        /* 평문 */
    const char *ctext;        /* 기대 암호문 */
    unsigned int klen;        /* 키 길이 */
    unsigned int len;         /* 데이터 길이 */
};

/* AEAD 테스트 벡터 */
struct aead_testvec {
    const char *key;
    const char *iv;
    const char *assoc;        /* AAD (추가 인증 데이터) */
    const char *ptext;        /* 평문 */
    const char *ctext;        /* 암호문 + 인증태그 */
    unsigned int klen;
    unsigned int alen;        /* AAD 길이 */
    unsigned int plen;        /* 평문 길이 */
    unsigned int clen;        /* 암호문+태그 길이 */
};

테스트 벡터 추가 방법

새 알고리즘의 테스트 벡터를 추가하려면 두 파일을 수정합니다:

/* 1단계: crypto/testmgr.h — 테스트 벡터 데이터 정의 */

static const struct hash_testvec myhash256_tv_template[] = {
    {   /* 벡터 #1: 빈 입력 */
        .plaintext = "",
        .psize     = 0,
        .digest    = "\xe3\xb0\xc4\x42...",  /* 기대 해시 값 */
    },
    {   /* 벡터 #2: "abc" */
        .plaintext = "abc",
        .psize     = 3,
        .digest    = "\xba\x78\x16\xbf...",
    },
    {   /* 벡터 #3: 긴 입력 (NIST 표준 벡터 등) */
        .plaintext = "abcdbcdecdefdefg...",
        .psize     = 56,
        .digest    = "\x24\x8d\x6a\x61...",
    },
};

/* 2단계: crypto/testmgr.c — 테스트 벡터를 알고리즘에 연결 */

static const struct alg_test_desc alg_test_descs[] = {
    /* ... 기존 항목들 (알파벳순 정렬!) ... */
    {
        .alg  = "myhash256",
        .test = alg_test_hash,      /* 테스트 함수 유형 */
        .suite = {
            .hash = __VECS(myhash256_tv_template)
        }
    },
    /* ... */
};
알파벳순 정렬 필수:

alg_test_descs[] 배열은 알고리즘 이름의 알파벳순으로 정렬되어 있어야 합니다. 커널은 이진 탐색으로 테스트 항목을 찾으므로, 정렬이 어긋나면 테스트가 실행되지 않습니다.

tcrypt — 성능 벤치마크

crypto/tcrypt.c는 암호 알고리즘의 성능을 측정하는 커널 모듈(Kernel Module)입니다:

# tcrypt 모듈 로드 — mode 번호로 테스트 유형 선택
modprobe tcrypt mode=200    # SHA-256 속도 테스트
modprobe tcrypt mode=500    # AES-CBC 속도 테스트
modprobe tcrypt mode=300    # AEAD 계열 속도 테스트

# 결과 확인
dmesg | tail -50
# testing speed of async cbc(aes) encryption
# test 0 (128 bit key, 16 byte blocks): 1 operation in 1234 cycles (16 bytes)
# test 1 (128 bit key, 64 byte blocks): 1 operation in 2345 cycles (64 bytes)

# 모든 알고리즘 셀프 테스트 실행
modprobe tcrypt mode=0      # 전체 알고리즘 테스트

# 특정 알고리즘만 테스트 (sec 파라미터로 벤치마크 시간 조절)
modprobe tcrypt mode=500 sec=5   # 5초간 벤치마크
tcrypt mode테스트 대상
0모든 알고리즘 자가 테스트 (KAT)
200~215해시 속도 (SHA-1, SHA-256, MD5, BLAKE2 등)
300~399AEAD 속도 (AES-GCM, ChaCha20-Poly1305 등)
500~599skcipher 속도 (AES-CBC, AES-CTR, AES-XTS 등)

Kconfig / Makefile 통합

커널 트리에 새 알고리즘을 통합하려면 crypto/ 디렉터리의 Kconfig와 Makefile을 수정합니다:

# crypto/Kconfig — 알고리즘 빌드 옵션 추가

config CRYPTO_MYHASH256
    tristate "My Hash 256 algorithm"
    select CRYPTO_HASH           # 해시 프레임워크 의존성
    help
      This is an example hash algorithm producing a 256-bit digest.

config CRYPTO_MYCIPHER
    tristate "My Block Cipher (CBC mode)"
    select CRYPTO_SKCIPHER       # skcipher 프레임워크 의존성
    select CRYPTO_LIB_AES        # AES 라이브러리 의존성 (필요 시) 
    help
      My custom block cipher with CBC mode support.

# H/W 가속 구현인 경우
config CRYPTO_MYCIPHER_X86_64
    tristate "My Block Cipher (x86_64/AES-NI 가속)"
    depends on X86 && 64BIT
    select CRYPTO_MYCIPHER       # generic 구현 fallback용 
    select CRYPTO_SIMD           # SIMD 헬퍼 */
    help
      x86_64 AES-NI accelerated implementation.
crypto/Makefile — 빌드 규칙 구조 CONFIG_CRYPTO_MYHASH256 obj-$(...) myhash256.o ← 단일 소스 파일 CONFIG_CRYPTO_MYCIPHER obj-$(...) mycipher.o ← 단일 소스 파일 CONFIG_CRYPTO_MYCIPHER_X86_64 obj-$(...) mycipher-x86_64.o ← 복합 객체 mycipher-x86_64-y := mycipher_glue.o ← C 글루 코드 mycipher-x86_64-asm.o ← 어셈블리 구현 Makefile 구조 요약 CONFIG_*=y → obj-y에 추가 → 커널 내장 / CONFIG_*=m → obj-m에 추가 → 모듈(.ko) / CONFIG_*=n → 빌드 제외

주요 Kconfig select 대상

의존성설명사용 경우
CRYPTO_HASH해시 프레임워크 (shash/ahash)해시 알고리즘
CRYPTO_SKCIPHER대칭 암호 프레임워크skcipher 알고리즘
CRYPTO_AEADAEAD 프레임워크AEAD 알고리즘
CRYPTO_SIMDSIMD 컨텍스트 관리 헬퍼SSE/AVX/NEON 사용 구현
CRYPTO_LIB_AESAES 키 스케줄 라이브러리AES 기반 구현
CRYPTO_LIB_SHA256SHA-256 라이브러리 함수SHA-256 기반 구현

MODULE_ALIAS_CRYPTO 매크로(Macro)

/* 모듈 자동 로딩을 위한 alias 등록 */
MODULE_ALIAS_CRYPTO("myhash256");
MODULE_ALIAS_CRYPTO("myhash256-generic");

/* 커널이 "myhash256" 알고리즘을 요청하면:
 * 1. crypto_has_alg("myhash256") → 등록 안 됨
 * 2. request_module("crypto-myhash256") 호출
 * 3. MODULE_ALIAS_CRYPTO 매칭 → myhash256.ko 자동 로드
 * 4. module_init 실행 → crypto_register_shash()
 * 5. 이제 crypto_alloc_shash("myhash256") 성공
 */

cryptomgr와 알고리즘 탐색 메커니즘

cryptomgr(Crypto Manager)는 알고리즘 등록/해제/탐색을 관리하는 커널 서브시스템입니다:

알고리즘 탐색/등록 흐름 crypto_alloc_skcipher() "cbc(aes)" 요청 crypto_alg_lookup() 등록된 알고리즘 검색 Found! priority 최고 선택 crypto_larval 생성 대기 객체 등록 request_module() crypto-cbc + crypto-aes Template 매칭 cbc(X) → cbc + aes 인스턴스 자동 생성 cbc(aes) 등록 완료 testmgr 자동 검증 있음 없음
알고리즘 탐색 흐름: lookup → (없으면) larval → modprobe → template 매칭 → 인스턴스 생성 → 테스트

crypto_larval — 알고리즘 대기 객체

/* crypto/api.c — 알고리즘 탐색 핵심 로직 (간략화) */

struct crypto_alg *crypto_alg_mod_lookup(const char *name,
                                         u32 type, u32 mask)
{
    struct crypto_alg *alg;

    /* 1. 이미 등록된 알고리즘 검색 */
    alg = crypto_alg_lookup(name, type, mask);
    if (alg)
        return alg;

    /* 2. 모듈 자동 로드 시도 (MODULE_ALIAS_CRYPTO 매칭) */
    request_module("crypto-%s", name);

    /* 3. larval (대기 객체) 생성 — 비동기 등록 대기 */
    struct crypto_larval *larval = crypto_larval_alloc(name, type, mask);

    /* 4. cryptomgr에 알고리즘 탐색 요청 (kthread) */
    crypto_probing_notify(CRYPTO_MSG_ALG_REQUEST, larval);

    /* 5. 등록 완료 대기 (타임아웃 60초) */
    alg = crypto_larval_wait(larval);

    /* cryptomgr는 템플릿 매칭을 시도:
     * "cbc(aes)" → cbc 템플릿 + aes 알고리즘으로 분해
     * → cbc 템플릿의 create() 호출
     * → cbc(aes) 인스턴스 자동 생성 및 등록
     */

    return alg;
}

FIPS 모드와 자가 테스트

FIPS(Federal Information Processing Standards)는 미국 NIST(National Institute of Standards and Technology)가 발행하는 연방 정보 처리 표준입니다. 암호화 모듈의 보안 요구사항을 정의하며, 미국 연방 정부 기관은 물론 금융, 의료, 방위산업 등 규제 환경에서 필수적으로 요구됩니다. 리눅스 커널은 부트 파라미터를 통해 FIPS 모드를 활성화하여 인증된 암호 알고리즘만 사용하도록 강제할 수 있습니다.

FIPS 140 표준 개요

FIPS 140은 암호화 모듈의 보안 요구사항을 정의하는 NIST 표준으로, 현재 FIPS 140-3(ISO/IEC 19790:2012 기반)이 최신 버전입니다. FIPS 140-2는 2021년 9월 이후 신규 인증이 중단되었으나, 기존 인증은 유효합니다.

보안 레벨요구사항적용 예
Level 1승인된 알고리즘/함수 사용, 소프트웨어 전용 구현 허용리눅스 커널 Crypto Framework (Crypto API) (소프트웨어)
Level 2Level 1 + 물리적 탬퍼 증거(tamper-evidence), 역할 기반 인증HSM(Hardware Security Module) 기본 등급
Level 3Level 2 + 물리적 탬퍼 저항(tamper-resistance), ID 기반 인증결제 단말기, 군사용 장비
Level 4Level 3 + 환경적 공격 방어 (전압, 온도 변조 감지)최고 보안 등급 하드웨어 모듈
ℹ️

리눅스 커널의 FIPS 적용: 커널 Crypto Framework (Crypto API)는 FIPS 요구사항(자가 테스트, 승인 알고리즘 정책, 무결성 검증 연계)의 기술적 기반을 제공합니다. 배포판별 인증 보유 여부와 지원 범위는 릴리스/벤더 정책에 따라 수시로 달라지므로, 최신 상태는 참고자료의 FIPS 지원 상태 섹션에서 확인하세요.

주요 FIPS/NIST 암호화 표준

표준 번호명칭내용커널 대응
FIPS 140-3Security Requirements for Cryptographic Modules암호 모듈 보안 요구사항 (4단계)fips_enabled 전역 변수, 자가 테스트
FIPS 197Advanced Encryption Standard (AES)128/192/256비트 대칭 블록 암호aes_generic, aesni_intel
FIPS 180-4Secure Hash Standard (SHS)SHA-1, SHA-224/256/384/512sha256_generic, sha512_generic
FIPS 198-1HMAC해시 기반 메시지 인증 코드hmac 템플릿
FIPS 186-5Digital Signature Standard (DSS)RSA, ECDSA, EdDSA 전자서명rsa_generic, ecdsa_generic
FIPS 202SHA-3 StandardSHA3-224/256/384/512, SHAKE128/256sha3_generic
SP 800-38ABlock Cipher ModesECB, CBC, CFB, OFB, CTRecb, cbc, ctr 템플릿
SP 800-38DGCM ModeGalois/Counter Mode (인증 암호화)gcm 템플릿, gcm-aes-aesni
SP 800-38FKey WrapAES Key Wrap (KW, KWP)kw(aes)
SP 800-56A/BKey EstablishmentDH, ECDH 키 합의dh_generic, ecdh_generic
SP 800-90ADRBG결정론적 난수 생성기 (CTR, Hash, HMAC)drbg_pr_*, drbg_nopr_*
SP 800-132PBKDF비밀번호 기반 키 유도사용자 공간에서 주로 사용

FIPS 모드 활성화

FIPS 140-3 인증이 필요한 환경에서는 커널을 FIPS 모드로 부팅합니다. 이 모드에서는 모든 암호 알고리즘이 반드시 자가 테스트(KAT: Known Answer Test)를 통과해야 사용 가능합니다:

# ─── FIPS 모드 활성화 (부트 파라미터) ───
# GRUB 설정: /etc/default/grub
GRUB_CMDLINE_LINUX="fips=1"
# /boot 파티션이 별도인 경우 boot= 지정 필요
GRUB_CMDLINE_LINUX="fips=1 boot=/dev/sda1"

# GRUB 설정 반영
grub2-mkconfig -o /boot/grub2/grub.cfg

# ─── 현재 FIPS 모드 확인 ───
cat /proc/sys/crypto/fips_enabled
# 1 = FIPS 모드, 0 = 일반 모드

# sysctl로도 확인 가능
sysctl crypto.fips_enabled
# crypto.fips_enabled = 1

# FIPS 모드에서 알고리즘 등록 실패 시 로그
dmesg | grep -i fips
# alg: fips already enabled
# alg: self-tests for myhash256 (myhash256-generic) failed
# (result -2) → 테스트 벡터 불일치
⚠️

FIPS 모드 전환 시 주의: FIPS 모드는 런타임에 동적으로 전환할 수 없습니다. 반드시 부트 파라미터 fips=1로 커널 초기화 시점에 활성화해야 합니다. 이는 부팅 초기 단계에서 암호 모듈 무결성 검증과 자가 테스트가 수행되어야 하기 때문입니다. /proc/sys/crypto/fips_enabled는 읽기 전용(Read-Only)이며 쓰기가 불가합니다.

FIPS 모드 커널 내부 구현

커널 내부에서 FIPS 모드는 전역 변수 fips_enabled로 관리됩니다. 이 변수는 부팅 시 설정되며, 암호 서브시스템 전반에서 참조합니다:

/* crypto/fips.c — FIPS 모드 전역 상태 */
#include <linux/kernel.h>
#include <linux/sysctl.h>
#include <linux/export.h>

int fips_enabled;
EXPORT_SYMBOL_GPL(fips_enabled);

/* 부트 파라미터 "fips=1" 처리 */
static int __init fips_enable(char *str)
{
    fips_enabled = 1;
    pr_info("fips mode is enabled\\n");
    return 1;
}
__setup("fips=", fips_enable);

/* /proc/sys/crypto/fips_enabled sysctl 등록 */
static struct ctl_table crypto_sysctl_table[] = {
    {
        .procname   = "fips_enabled",
        .data       = &fips_enabled,
        .maxlen     = sizeof(int),
        .mode       = 0444,            /* 읽기 전용 */
        .proc_handler = proc_dointvec,
    },
};

커널 코드 내에서 FIPS 모드를 확인하는 주요 인터페이스:

/* include/linux/fips.h */
#ifdef CONFIG_CRYPTO_FIPS
extern int fips_enabled;
#else
#define fips_enabled 0
#endif

/* 사용 예: 비승인 알고리즘 차단 */
if (fips_enabled && !(alg->cra_flags & CRYPTO_ALG_FIPS_INTERNAL)) {
    /* FIPS 비승인 알고리즘 → 등록 거부 */
    return -ENOENT;
}

/* CONFIG_CRYPTO_FIPS 커널 설정 옵션 */
/*
 * Kconfig (crypto/Kconfig):
 * config CRYPTO_FIPS
 *     bool "FIPS 200 compliance"
 *     depends on CRYPTO
 *     help
 *       This option enables the FIPS boot option which is
 *       required if you want the system to operate in a
 *       FIPS 200 compliant manner.
 */

자가 테스트(Self-Test) 메커니즘

FIPS 140-3은 암호 모듈이 전원 투입 자가 테스트(Power-On Self-Test, POST)조건부 자가 테스트(Conditional Self-Test, CST)를 수행할 것을 요구합니다. 리눅스 커널에서는 crypto/testmgr.c가 이 역할을 담당합니다:

/* crypto/testmgr.c — 알고리즘 자가 테스트 핵심 구조 */

/* KAT(Known Answer Test) 테스트 벡터 구조체 */
struct cipher_testvec {
    const char *key;        /* 입력 키 */
    const char *iv;         /* 초기화 벡터 */
    const char *ptext;      /* 평문 (Known Input) */
    const char *ctext;      /* 암호문 (Known Answer) */
    unsigned int klen;      /* 키 길이 */
    unsigned int len;       /* 데이터 길이 */
    bool fips_skip;         /* FIPS 모드에서 건너뛸지 여부 */
};

/* 해시 테스트 벡터 */
struct hash_testvec {
    const char *key;        /* HMAC 키 (해시는 NULL) */
    const char *plaintext;  /* 입력 데이터 */
    const char *digest;     /* 기대 출력 (Known Answer) */
    unsigned int psize;     /* 평문 길이 */
    unsigned int ksize;     /* 키 길이 */
};

/* 알고리즘별 테스트 정의 — testmgr.c의 핵심 테이블 */
static const struct alg_test_desc alg_test_descs[] = {
    {
        .alg   = "cbc(aes)",
        .test  = alg_test_skcipher,
        .fips_allowed = 1,     /* FIPS 승인 알고리즘 */
        .suite = {
            .cipher = __VECS(aes_cbc_tv_template)
        }
    }, {
        .alg   = "ecb(aes)",
        .test  = alg_test_skcipher,
        .fips_allowed = 1,
        .suite = {
            .cipher = __VECS(aes_tv_template)
        }
    }, {
        .alg   = "sha256",
        .test  = alg_test_hash,
        .fips_allowed = 1,
        .suite = {
            .hash = __VECS(sha256_tv_template)
        }
    }, {
        .alg   = "hmac(sha256)",
        .test  = alg_test_hash,
        .fips_allowed = 1,
        .suite = {
            .hash = __VECS(hmac_sha256_tv_template)
        }
    },
    /* ... 수백 개의 알고리즘 테스트 정의 ... */
};

테스트 실행 흐름 — 알고리즘이 등록될 때 자동으로 호출됩니다:

/* crypto/testmgr.c — 자가 테스트 실행 흐름 */

/* 1. 알고리즘 등록 시 testmgr가 호출됨 */
int alg_test(const char *driver, const char *alg,
             u32 type, u32 mask)
{
    const struct alg_test_desc *test;
    int rc;

    /* alg_test_descs[] 테이블에서 알고리즘 검색 */
    test = find_test(alg);
    if (!test) {
        if (fips_enabled) {
            /* FIPS 모드: 테스트 없으면 → 등록 거부 */
            pr_err("alg: no test for %s (%s)\\n", alg, driver);
            return -EINVAL;
        }
        /* 일반 모드: 테스트 없으면 → 건너뜀 (경고만) */
        return 0;
    }

    /* 2. FIPS 모드에서 비승인 알고리즘 차단 */
    if (fips_enabled && !test->fips_allowed) {
        pr_info("alg: %s not allowed in fips mode\\n", alg);
        return -EINVAL;
    }

    /* 3. KAT(Known Answer Test) 실행 */
    rc = test->test(test, driver, type, mask);
    if (rc) {
        if (fips_enabled)
            panic("alg: self-tests for %s (%s) failed (rc=%d)\\n",
                  alg, driver, rc);
        else
            pr_warn("alg: self-tests for %s (%s) failed (rc=%d)\\n",
                    alg, driver, rc);
    }

    return rc;
}
코드 설명

crypto/testmgr.calg_test()는 알고리즘 등록 시 자동 호출되어 KAT(Known Answer Test)를 수행하는 자가 테스트 진입점입니다.

  • find_test(alg)alg_test_descs[] 테이블에서 알고리즘 이름으로 이진 탐색합니다. 테이블이 알파벳순 정렬이므로 O(log n) 검색이 가능합니다.
  • FIPS 모드 + 테스트 없음FIPS 모드에서 테스트 벡터가 없는 알고리즘은 -EINVAL로 등록이 거부됩니다. 일반 모드에서는 경고만 출력하고 등록을 허용합니다.
  • fips_allowed 검사FIPS 모드에서 fips_allowed가 0인 비승인 알고리즘(예: DES, MD5)은 사용이 차단됩니다.
  • test->test() 호출알고리즘 유형별 테스트 함수(alg_test_skcipher, alg_test_hash, alg_test_aead 등)를 호출하여 KAT를 실행합니다.
  • 테스트 실패 시 panic()FIPS 모드에서 자가 테스트 실패는 panic()으로 시스템을 즉시 중단합니다. 일반 모드에서는 경고 로그만 기록합니다.
⚠️

FIPS 모드 동작 주의: FIPS 모드에서 자가 테스트 실패 시 동작은 커널 버전/패치(Patch)셋/배포판 정책에 따라 다를 수 있습니다. 일반적으로 해당 알고리즘 비활성화 또는 시스템 중단(panic()) 정책 중 하나가 적용되므로, 운영 환경에서는 반드시 배포판 보안 가이드를 기준으로 확인해야 합니다.

FIPS 자가 테스트 유형

FIPS 140-3이 요구하는 자가 테스트는 크게 두 가지로 분류됩니다:

테스트 유형실행 시점목적커널 구현
POST (Power-On Self-Test) 부팅 시 / 모듈 로드 시 암호 알고리즘의 올바른 동작 확인 testmgr.c의 KAT 벡터 테스트
무결성 테스트 부팅 시 / 모듈 로드 시 암호 모듈 바이너리 변조 감지 HMAC-SHA256 기반 모듈 무결성 검증
CST (Conditional Self-Test) 특정 조건 발생 시 키 쌍 일관성, 난수 연속성 등 DRBG 연속 출력 비교, RSA 키 쌍 검증
KAT (Known Answer Test) 알고리즘 등록 시 알려진 입력→출력 쌍으로 정확성 검증 testmgr.h의 테스트 벡터
PCT (Pairwise Consistency Test) 비대칭 키 생성 시 공개키-개인키 쌍의 일관성 RSA/ECDSA 키 생성 후 서명-검증
/* KAT 실행 예: 대칭 암호(skcipher) 테스트 */
static int test_skcipher_vec(const char *driver,
                              const struct cipher_testvec *vec,
                              struct skcipher_request *req)
{
    /* 1. 키 설정 */
    crypto_skcipher_setkey(tfm, vec->key, vec->klen);

    /* 2. 알려진 평문으로 암호화 수행 */
    sg_init_one(&sg, buf, vec->len);
    memcpy(buf, vec->ptext, vec->len);
    skcipher_request_set_crypt(req, &sg, &sg, vec->len, iv);
    err = crypto_skcipher_encrypt(req);

    /* 3. 결과를 Known Answer와 비교 */
    if (memcmp(buf, vec->ctext, vec->len) != 0) {
        pr_err("encryption test failed for %s\\n", driver);
        return -EINVAL;   /* KAT 실패 */
    }

    /* 4. 역방향(복호화) 테스트 */
    memcpy(buf, vec->ctext, vec->len);
    err = crypto_skcipher_decrypt(req);
    if (memcmp(buf, vec->ptext, vec->len) != 0) {
        pr_err("decryption test failed for %s\\n", driver);
        return -EINVAL;   /* KAT 실패 */
    }

    return 0;  /* 테스트 통과 */
}

/* 조건부 자가 테스트 예: DRBG 연속 출력 비교 */
static int drbg_healthcheck(struct drbg_state *drbg,
                             unsigned char *buf)
{
    /* FIPS 140-3: 난수 생성기 연속 출력 비교 테스트 (CRNGT)
     * 연속된 두 출력 블록이 동일하면 → 난수 생성기 고장 */
    if (memcmp(buf, drbg->prev_output, drbg->len) == 0) {
        pr_err("drbg: continuous test failed\\n");
        if (fips_enabled)
            panic("drbg: continuous random number test failed\\n");
        return -EFAULT;
    }
    memcpy(drbg->prev_output, buf, drbg->len);
    return 0;
}
코드 설명

crypto/testmgr.c의 KAT(Known Answer Test) 실행 코드로, 대칭 암호의 정확성 검증과 DRBG 난수 생성기의 건전성 검사를 보여줍니다.

  • test_skcipher_vec()테스트 벡터(cipher_testvec)의 알려진 키·평문으로 암호화를 수행한 뒤, 결과를 기대 암호문(vec->ctext)과 바이트 단위로 비교합니다.
  • sg_init_one() + in-place 처리단일 scatterlist 엔트리로 버퍼를 설정하고, src와 dst를 같은 sg로 지정하여 in-place 암·복호화를 수행합니다.
  • 양방향 검증암호화(평문→암호문)와 복호화(암호문→평문) 양쪽 모두 테스트하여 알고리즘의 왕복(round-trip) 정확성을 확인합니다.
  • drbg_healthcheck()FIPS 140-3 CRNGT(Continuous Random Number Generator Test)를 구현합니다. 난수 생성기의 연속 출력 두 블록이 동일하면 고장으로 판단하여 FIPS 모드에서는 panic()을 호출합니다.

FIPS 승인/비승인 알고리즘

FIPS 모드에서는 NIST가 승인한 알고리즘만 사용할 수 있습니다. testmgr.cfips_allowed 플래그로 구분합니다:

분류FIPS 승인 (사용 가능)FIPS 비승인 (차단)
블록 암호 AES-128/192/256, 3DES (제한적) Blowfish, Twofish, Serpent, Camellia, CAST5/6, DES
운용 모드 ECB, CBC, CTR, GCM, CCM, XTS, KW
해시 SHA-1 (서명 검증만), SHA-224/256/384/512, SHA3-* MD4, MD5, RIPEMD-160
MAC HMAC-SHA-*, CMAC-AES, GMAC HMAC-MD5
비대칭 RSA (≥2048bit), ECDSA (P-256/384/521), EdDSA RSA <2048bit
키 합의 DH (≥2048bit), ECDH (P-256/384/521) DH <2048bit
난수 생성 CTR_DRBG, Hash_DRBG, HMAC_DRBG ANSI X9.31 PRNG (폐기됨)
키 유도 SP 800-108 KDF (HMAC/CMAC 기반)
💡

3DES 주의: NIST SP 800-131A Rev.2에 따라 3DES(Triple DES)는 2023년 이후 암호화에 사용이 금지(disallowed)되었으며, 복호화에만 제한적으로 허용됩니다. 새로운 구현에서는 반드시 AES를 사용하세요.

CRYPTO_ALG_FIPS_INTERNAL 플래그

커널은 CRYPTO_ALG_FIPS_INTERNAL 플래그로 FIPS 인증 경계(Boundary) 내의 알고리즘과 내부 도우미 알고리즘을 구분합니다:

/* include/linux/crypto.h */

/* FIPS 내부 전용 알고리즘 표시 — 정확한 비트값은 커널 헤더 기준 */
#define CRYPTO_ALG_FIPS_INTERNAL    /* include/linux/crypto.h 참조 */

/*
 * 이 플래그가 설정된 알고리즘:
 *   - FIPS 모듈 경계 내부에서만 사용 가능
 *   - AF_ALG 소켓을 통한 사용자 공간 접근 차단
 *   - /proc/crypto에서 "internal : yes"로 표시
 *
 * 예: CTR 모드 DRBG의 내부 AES 인스턴스
 *     → 직접 사용자 접근은 차단, DRBG 내부에서만 사용
 */

/* AF_ALG에서 FIPS_INTERNAL 알고리즘 차단 */
static int alg_accept(struct sock *sk)
{
    struct crypto_alg *alg = ...;

    /* FIPS 모드: internal 플래그 알고리즘은 AF_ALG로 사용 불가 */
    if (alg->cra_flags & CRYPTO_ALG_FIPS_INTERNAL) {
        if (fips_enabled)
            return -ENOENT;
    }
    ...
}

/* 템플릿에서 FIPS_INTERNAL 전파 예 */
static int cbc_create(struct crypto_template *tmpl,
                      struct rtattr **tb)
{
    /* 내부 알고리즘(예: ecb(aes))은 FIPS_INTERNAL로 마킹 */
    inst->alg.base.cra_flags |= (alg->cra_flags & CRYPTO_ALG_FIPS_INTERNAL);
    ...
}

HMAC 무결성 검증

FIPS 140-3은 암호화 모듈의 바이너리 무결성을 부팅 시 검증할 것을 요구합니다. 리눅스에서는 HMAC-SHA256 기반의 무결성 검증을 수행합니다:

FIPS HMAC 무결성 검증 흐름 빌드 시 1) 커널 이미지(vmlinuz) 빌드 2) HMAC-SHA256(vmlinuz) 계산 3) /boot/.vmlinuz*.hmac 저장 부팅 시 (fips=1) 1) initramfs 단계에서 fips-mode-setup 실행 2) vmlinuz HMAC 재계산 3) .vmlinuz.hmac와 비교 4) 불일치: panic() 또는 부팅 중단 5) 일치: 정상 부팅 진행 모듈 로드 시 1) crypto 관련 .ko 로드 요청 2) 모듈 HMAC 재계산 후 .ko.hmac와 비교 3) 불일치 시 모듈 로드 거부
# FIPS 무결성 파일 확인 (RHEL/CentOS 예시)
ls -la /boot/.vmlinuz-$(uname -r).hmac
# -r--------. 1 root root 33 Jan 15 12:00 /boot/.vmlinuz-6.x.hmac

# 커널 모듈 HMAC 파일
ls /lib/modules/$(uname -r)/.*.hmac
# .aesni-intel.ko.hmac
# .sha256_ssse3.ko.hmac
# .ghash-clmulni-intel.ko.hmac

# 수동 무결성 검증
sha256hmac /boot/vmlinuz-$(uname -r)
# 출력된 HMAC 값이 .vmlinuz-*.hmac 파일 내용과 일치해야 함

# FIPS 모듈 목록 확인 (dracut 기준)
cat /etc/dracut.conf.d/40-fips.conf
# add_dracutmodules+=" fips "

일반 모드 vs FIPS 모드 상세 비교

구분일반 모드FIPS 모드
자가 테스트 실패경고 로그 또는 알고리즘 비활성화 (구현별 상이)알고리즘 비활성화 또는 시스템 중단(정책 의존)
테스트 벡터 없음알고리즘별로 처리 방식 상이정책에 따라 등록 거부 가능
비승인 알고리즘사용 가능등록 거부 또는 CRYPTO_ALG_FIPS_INTERNAL로 제한
무결성 검증없음커널/모듈 HMAC-SHA256 무결성 검증
난수 생성기모든 RNG 사용 가능DRBG(SP 800-90A)만 허용, CRNGT 필수
키 길이 제한없음RSA ≥2048bit, DH ≥2048bit 등 최소 길이 강제
AF_ALG 접근모든 알고리즘FIPS 승인 + 비-internal 알고리즘만
알고리즘 우선순위priority 값 기준FIPS 승인 알고리즘 우선 (비승인 fallback 차단)
/proc/crypto모든 알고리즘 표시FIPS 승인 알고리즘만 외부 노출
커널 설정CONFIG_CRYPTOCONFIG_CRYPTO + CONFIG_CRYPTO_FIPS

FIPS 모드가 영향을 미치는 커널 서브시스템

FIPS 모드 활성화는 Crypto Framework (Crypto API)뿐 아니라 암호화를 사용하는 모든 커널 서브시스템에 영향을 미칩니다:

서브시스템FIPS 모드 영향구체적 변경
dm-crypt FIPS 승인 암호만 허용 aes-xts-plain64 사용, serpent/twofish 차단
IPsec (XFRM) FIPS 승인 알고리즘만 SA 설정 가능 AES-GCM, AES-CBC + HMAC-SHA256 허용, Blowfish/CAST 차단
IKE (strongSwan/Libreswan) FIPS 정책 연동 DH group 14(2048bit) 이상만 허용
TLS (kTLS) FIPS 승인 cipher suite만 AES-128/256-GCM, AES-256-CCM 허용
네트워크 (TCP-MD5) MD5 기반 인증 차단 FIPS 모드에서 TCP-MD5 옵션 비활성화
디스크 암호화 (LUKS) FIPS 호환 파라미터 강제 PBKDF2-SHA256 + AES-256-XTS, argon2id 차단
난수 (/dev/random) DRBG 기반으로 전환 SP 800-90A DRBG, CRNGT(연속 출력 비교) 활성화
모듈 서명 SHA-256/384/512만 허용 CONFIG_MODULE_SIG_HASH에 SHA-1 사용 불가
커널 키링(Keyring) FIPS 승인 알고리즘으로 키 연산 RSA ≥2048bit, ECDSA P-256+ 인증서만 허용
ℹ️

배포판/인증 상태 분리: 배포판별 FIPS 인증 보유 여부, 활성화 절차, CAVP/CMVP 진행 상태는 시간이 지나면 빠르게 바뀝니다. 이 페이지에서는 커널 내부 동작에 집중하고, 운영 상태 정보는 참고자료 - FIPS 지원/인증 상태로 위임합니다.

FIPS 모드 디버깅(Debugging)

# ─── FIPS 모드 상태 종합 확인 ───

# 1. FIPS 모드 활성화 여부
cat /proc/sys/crypto/fips_enabled

# 2. 부팅 시 FIPS 관련 커널 로그
dmesg | grep -i fips
# [    0.000000] command line: ... fips=1
# [    0.123456] fips mode is enabled

# 3. 자가 테스트 결과 확인
dmesg | grep "alg: self-tests"
# alg: self-tests for sha256 (sha256-generic) passed
# alg: self-tests for aes (aes-generic) passed
# alg: self-tests for cbc(aes) (cbc-aes-aesni) passed

# 4. FIPS 승인 알고리즘 목록 확인
grep -A1 "selftest" /proc/crypto | grep -B1 "passed"

# 5. FIPS 모드에서 차단된 알고리즘 확인
dmesg | grep "not allowed in fips"
# alg: md5 not allowed in fips mode

# 6. 무결성 검증 실패 확인
dmesg | grep -i "integrity"
# integrity: HMAC check failed for vmlinuz

# 7. 커널 FIPS 설정 옵션 확인
zgrep CONFIG_CRYPTO_FIPS /proc/config.gz
# CONFIG_CRYPTO_FIPS=y

# 8. 현재 사용 중인 알고리즘의 FIPS 상태
# /proc/crypto에서 fips 관련 필드 확인
awk '/^name/{name=$NF} /^selftest/{print name, $NF}' /proc/crypto

# 9. FIPS 모드에서 OpenSSL 상태 확인 (사용자 공간 연동)
openssl version
openssl list -providers
# fips 프로바이더가 활성화되어 있어야 함
⚠️

FIPS 모드 트러블슈팅: (1) 부팅 실패 시 fips=1 제거 후 재부팅하여 원인 파악, (2) 모듈 로드 실패 시 .hmac 파일 존재 여부와 커널 버전 일치 확인, (3) 애플리케이션 오류 시 crypto-policies 설정과 라이브러리 FIPS 모드 연동 확인, (4) VM 환경에서는 호스트의 AES-NI/SHA-NI CPU 플래그 패스스루 확인이 필요합니다.

/proc/crypto 상세 해석

/proc/crypto는 현재 등록된 모든 알고리즘의 상세 정보를 보여줍니다:

# /proc/crypto 출력 예시 — AES-NI CBC 구현
name         : cbc(aes)
driver       : cbc-aes-aesni
module       : aesni_intel
priority     : 400
refcnt       : 9
selftest     : passed
internal     : no
type         : skcipher
async        : no
blocksize    : 16
min keysize  : 16
max keysize  : 32
ivsize       : 16
chunksize    : 16
walksize     : 16
필드의미확인 포인트
name정규 알고리즘 이름crypto_alloc_*()에서 사용하는 이름
driver드라이버 고유 이름어떤 구현체인지 식별 (generic, aesni, qat 등)
module소속 커널 모듈lsmod와 대조하여 모듈 의존성 확인
priority우선순위같은 name 중 가장 높은 값이 실제 사용됨
refcnt현재 참조 횟수(Reference Count)0이 아니면 사용 중 (모듈 언로드 불가)
selftest자가 테스트 결과passed / unknown / not available
internal내부 전용 여부yes면 AF_ALG에서 사용 불가
async비동기 여부H/W 가속기 구현은 보통 yes
# 실용적인 /proc/crypto 분석 명령

# 같은 알고리즘의 모든 구현체와 priority 비교
awk '/^name/{n=$3} /^driver/{d=$3} /^priority/{print n, d, $3}' /proc/crypto \
  | sort | column -t

# 특정 알고리즘의 실제 사용되는 구현 확인 (priority 최고)
awk '/^name/{n=$3} /^driver/{d=$3} /^priority/{p=$3}
     n=="cbc(aes)"{print p, d}' /proc/crypto | sort -rn | head -1

# 자가 테스트 실패한 알고리즘 찾기
awk '/^name/{n=$3} /^selftest/{if($3!="passed") print n, $3}' /proc/crypto

# 비동기(H/W) 알고리즘만 확인
awk '/^name/{n=$3} /^async/{if($3=="yes") print n}' /proc/crypto

SIMD 컨텍스트 관리

SSE/AVX/NEON 명령어를 사용하는 H/W 가속 구현에서는 SIMD 레지스터 컨텍스트 관리가 필수입니다. 커널에서 SIMD 레지스터를 사용하려면 명시적으로 저장/복원해야 합니다:

#include <asm/fpu/api.h>        /* x86 */
#include <crypto/internal/simd.h>

/* 방법 1: 직접 kernel_fpu_begin/end 사용 */
static int my_aesni_encrypt(struct skcipher_request *req)
{
    /* kernel_fpu_begin()은 현재 태스크의 FPU 상태를 저장하고
     * 커널이 SSE/AVX 레지스터를 사용할 수 있게 함
     * 주의: 이 구간에서는 preemption이 비활성화됨 */
    kernel_fpu_begin();

    /* AES-NI 명령어 사용 (인라인 어셈블리 또는 .S 파일) */
    aesni_cbc_enc(ctx->key_sched, dst, src, len, iv);

    kernel_fpu_end();
    return 0;
}

/* 방법 2: crypto_simd 래퍼 사용 (권장) */
/* softirq/hardirq 컨텍스트에서도 안전하게 동작
 * SIMD를 사용할 수 없는 컨텍스트에서는 자동으로
 * cryptd kthread로 작업을 위임 */
static struct simd_skcipher_alg *simd_alg;

static int __init my_simd_init(void)
{
    int err;

    /* 원본 SIMD 알고리즘 등록 (__cbc-aes-aesni, INTERNAL 플래그) */
    err = crypto_register_skcipher(&my_simd_skcipher);
    if (err)
        return err;

    /* SIMD 래퍼 등록 (cbc-aes-aesni, 외부 공개)
     * → process context: 직접 SIMD 실행
     * → softirq context: cryptd kthread로 위임 */
    simd_alg = simd_skcipher_create_compat(
        my_simd_skcipher.base.cra_name,
        my_simd_skcipher.base.cra_driver_name,
        my_simd_skcipher.base.cra_name);

    return PTR_ERR_OR_ZERO(simd_alg);
}
커널에서 SIMD 사용 시 주의:
  • kernel_fpu_begin()kernel_fpu_end() 사이에서는 sleep 불가 (preemption 비활성화)
  • softirq/hardirq 컨텍스트에서는 kernel_fpu_begin() 호출 불가 — crypto_simd 래퍼를 사용하거나, irq_fpu_usable()로 확인 후 fallback
  • 대량 데이터 처리 시 주기적으로 kernel_fpu_end(); kernel_fpu_begin();으로 preemption 허용 (latency 방지)
  • ARM의 경우 kernel_neon_begin() / kernel_neon_end() 사용

알고리즘 구현 체크리스트

#단계파일핵심 확인사항
1알고리즘 구현crypto/my_alg.c콜백 함수 모두 구현, 에러 처리, 메모리 해제
2헤더 포함crypto/my_alg.c<crypto/internal/hash.h> 등 올바른 헤더
3알고리즘 등록crypto/my_alg.ccrypto_register_*() + module_init/exit
4MODULE_ALIAScrypto/my_alg.cMODULE_ALIAS_CRYPTO("name") 추가
5테스트 벡터crypto/testmgr.h최소 3개 이상의 KAT 벡터 (NIST 표준 권장)
6테스트 등록crypto/testmgr.calg_test_descs[]에 알파벳순 추가
7Kconfigcrypto/Kconfigconfig CRYPTO_MY_ALG + select 의존성
8Makefilecrypto/Makefileobj-$(CONFIG_CRYPTO_MY_ALG) 추가
9셀프 테스트부팅/로드 시dmesg | grep selftest로 passed 확인
10벤치마크런타임tcrypt 모듈로 성능 측정
💡

커널 소스 참고: 새 알고리즘을 구현할 때는 반드시 커널 트리의 유사 구현을 참고하세요. 해시는 crypto/sha256_generic.c, 블록 암호는 crypto/aes_generic.c, CBC 모드는 crypto/cbc.c, AEAD는 crypto/gcm.c, H/W 가속은 arch/x86/crypto/aesni-intel_glue.c가 좋은 참고 자료입니다.

crypto_alg 등록 흐름

알고리즘 등록은 Crypto Framework (Crypto API)의 핵심 진입점(Entry Point)입니다. crypto_register_alg() 호출 시 커널 내부에서는 이름 충돌 검사, 우선순위 비교, 테스트 벡터 검증, larval 깨우기(Wakeup), 템플릿 인스턴스화가 순차적으로 수행됩니다. 이 섹션에서는 등록 과정의 내부 흐름을 단계별로 추적합니다.

crypto_register_alg() 내부 흐름

crypto_register_alg()crypto/algapi.c에 정의되어 있으며, 호출 시 다음 순서로 동작합니다:

crypto_register_alg() 내부 흐름 1. 입력 검증 cra_name, blocksize, flags 2. 리스트 잠금 down_write(crypto_alg_sem) 3. 이름/드라이버 충돌 검사 같은 driver_name → -EEXIST 4. 리스트 삽입 list_add(&alg→cra_list) 5. larval 깨우기 대기 중 alloc 요청 완료 통보 6. 알림 체인 호출 CRYPTO_MSG_ALG_REGISTER 7. testmgr 자가 테스트 KAT 벡터 검증 (비동기) 8. 템플릿 인스턴스화 등록된 tmpl과 자동 조합 템플릿 인스턴스화 상세 새 블록 암호 "sm4" 등록 시: 1. 등록된 템플릿 목록(cbc, ctr, xts, gcm, ...)을 순회 2. 각 템플릿의 create() 콜백으로 "cbc(sm4)", "ctr(sm4)", "xts(sm4)" 자동 생성 3. 중첩 템플릿도 재귀적 처리: "authenc(hmac(sha256),cbc(sm4))" 등 4. 생성된 인스턴스도 testmgr로 자가 테스트 수행 후 사용 가능
crypto_register_alg(): 검증 → 리스트 삽입 → larval 깨우기 → 알림 → 테스트 → 템플릿 자동 조합
/* crypto/algapi.c — crypto_register_alg() 핵심 로직 (간략화) */

int crypto_register_alg(struct crypto_alg *alg)
{
    struct crypto_alg *q;
    int ret = 0;

    /* 1. 입력 유효성 검사 */
    if (!alg->cra_name[0] || !alg->cra_driver_name[0])
        return -EINVAL;

    /* blocksize가 PAGE_SIZE를 초과하면 거부 */
    if (alg->cra_blocksize > PAGE_SIZE)
        return -EINVAL;

    /* refcnt 초기화 */
    refcount_set(&alg->cra_refcnt, 1);

    /* 2. 전역 알고리즘 리스트 잠금 (write lock) */
    down_write(&crypto_alg_sem);

    /* 3. 같은 driver_name이 이미 등록되어 있는지 확인 */
    list_for_each_entry(q, &crypto_alg_list, cra_list) {
        if (!strcmp(q->cra_driver_name, alg->cra_driver_name)) {
            ret = -EEXIST;
            goto out_unlock;
        }
    }

    /* 4. 알고리즘 리스트에 삽입 */
    list_add(&alg->cra_list, &crypto_alg_list);

    /* 5. 대기 중인 larval 객체 깨우기
     * crypto_alloc_*()에서 대기 중이던 요청에 통보 */
    crypto_alg_finish_registration(alg);

    up_write(&crypto_alg_sem);

    /* 6. 알림 체인: testmgr, cryptomgr에 등록 이벤트 전달
     * → testmgr: 자가 테스트 스케줄링
     * → cryptomgr: 템플릿 인스턴스화 트리거 */
    crypto_probing_notify(CRYPTO_MSG_ALG_REGISTER, alg);

    return 0;

out_unlock:
    up_write(&crypto_alg_sem);
    return ret;
}
코드 설명

crypto/algapi.ccrypto_register_alg()는 새 암호 알고리즘을 커널 전역 리스트에 등록하는 핵심 함수입니다.

  • 입력 유효성 검사cra_namecra_driver_name이 비어 있으면 -EINVAL을 반환합니다. cra_blocksizePAGE_SIZE를 초과하는 경우도 거부하여 scatterlist 처리의 안전성을 보장합니다.
  • down_write(&crypto_alg_sem)전역 알고리즘 리스트를 보호하는 rw_semaphore의 write lock을 획득합니다. 등록은 배타적이어야 하므로 write lock을 사용합니다.
  • driver_name 중복 검사crypto_alg_list를 순회하며 동일한 cra_driver_name이 이미 있으면 -EEXIST를 반환합니다. 같은 cra_name은 허용되지만 driver_name은 고유해야 합니다.
  • crypto_alg_finish_registration()이 알고리즘을 기다리던 larval 객체들을 깨웁니다. crypto_alloc_*()에서 대기 중이던 호출자가 새로 등록된 알고리즘을 사용할 수 있게 됩니다.
  • crypto_probing_notify()등록 이벤트를 알림 체인으로 전달합니다. testmgr는 자가 테스트를 스케줄링하고, cryptomgr는 등록된 블록 암호에 대해 cbc, ctr, xts 등의 템플릿 인스턴스를 자동 생성합니다.

우선순위 선택과 동적 교체

같은 cra_name에 여러 구현이 등록되면, crypto_alloc_*() 시점에 priority가 가장 높은 구현이 선택됩니다. 나중에 더 높은 priority의 구현이 등록되면 새로운 tfm 할당부터 적용됩니다:

/* crypto/api.c — 알고리즘 선택 로직 (간략화) */

static struct crypto_alg *crypto_find_alg(const char *name,
                                            u32 type, u32 mask)
{
    struct crypto_alg *best = NULL;
    struct crypto_alg *q;

    list_for_each_entry(q, &crypto_alg_list, cra_list) {
        /* cra_name이 요청과 일치하는지 확인 */
        if (strcmp(q->cra_name, name) != 0)
            continue;

        /* type/mask 필터 적용 */
        if ((q->cra_flags ^ type) & mask)
            continue;

        /* priority가 더 높은 구현 선택 */
        if (!best || q->cra_priority > best->cra_priority)
            best = q;
    }

    return best;
}

/* 우선순위 예시:
 * "aes" → aes_generic(100) vs aes-aesni(300) vs qat_aes(4001)
 * → QAT 드라이버 로드 시 qat_aes(4001)가 자동 선택
 * → QAT 드라이버 unload 시 aes-aesni(300)로 자동 전환
 * 기존 tfm은 영향 없음, 새 crypto_alloc_*() 호출부터 적용 */
코드 설명

crypto/api.c의 알고리즘 선택 로직으로, crypto_alloc_*() 시점에 가장 적합한 구현을 탐색합니다.

  • crypto_find_alg()전역 crypto_alg_list를 순회하며 요청된 cra_name과 일치하는 알고리즘을 찾습니다. 이 탐색은 read lock 하에서 수행됩니다.
  • type/mask 필터(q->cra_flags ^ type) & mask 연산으로 호출자가 요구하는 속성(비동기, 커널 전용 등)을 충족하지 않는 알고리즘을 필터링합니다.
  • priority 비교조건을 만족하는 알고리즘 중 cra_priority가 가장 높은 것을 선택합니다. H/W 드라이버 로드·언로드에 따라 동적으로 최적 구현이 변경되며, 이미 할당된 tfm에는 영향을 주지 않습니다.

템플릿 인스턴스화 메커니즘

템플릿은 알고리즘 등록 시 CRYPTO_MSG_ALG_REGISTER 알림을 받아 자동으로 인스턴스를 생성합니다. 이 메커니즘을 통해 새 블록 암호를 추가하면 기존 모든 운용 모드와 자동 조합됩니다:

/* crypto/algboss.c — 템플릿 인스턴스화 트리거 */

static int cryptomgr_notify(struct notifier_block *this,
                            unsigned long msg, void *data)
{
    switch (msg) {
    case CRYPTO_MSG_ALG_REQUEST:
        /* 알고리즘 요청 시: 템플릿 매칭 → 인스턴스 생성 */
        return cryptomgr_schedule_probe(data);

    case CRYPTO_MSG_ALG_REGISTER:
        /* 새 알고리즘 등록 시: 대기 중인 larval과 매칭 */
        return cryptomgr_schedule_test(data);
    }
    return NOTIFY_DONE;
}

/* 인스턴스 생성 과정 (예: "cbc(sm4)" 요청 시) */
/*
 * 1. "cbc(sm4)" 파싱 → 템플릿 이름 "cbc" + 내부 알고리즘 "sm4"
 * 2. crypto_template 목록에서 "cbc" 탐색
 * 3. cbc_tmpl->create() 호출:
 *    a. sm4 알고리즘 참조 (spawn) 획득
 *    b. skcipher_instance 할당
 *    c. 이름/속성 설정: cra_name = "cbc(sm4)"
 *    d. 내부 알고리즘 속성 상속 (blocksize, keysize 등)
 *    e. skcipher_register_instance() 로 등록
 * 4. 등록 완료 → 대기 중인 larval 깨우기
 * 5. testmgr가 자가 테스트 수행
 */
💡

등록 순서와 모듈 의존성: 템플릿 인스턴스화는 내부 알고리즘이 먼저 등록되어 있어야 합니다. "cbc(aes)"를 요청하면 커널은 먼저 crypto-aes 모듈 로드를 시도한 후 crypto-cbc 템플릿 모듈을 로드합니다. 두 모듈이 모두 로드되면 cbc 템플릿의 create()가 호출되어 인스턴스가 자동 생성됩니다. 이 과정에서 MODULE_ALIAS_CRYPTO 매크로가 핵심 역할을 합니다.

AEAD 상태 머신

AEAD(Authenticated Encryption with Associated Data) 알고리즘은 암호화와 인증을 하나의 원자적 연산(Atomic Operation)으로 결합합니다. 내부적으로 encrypt 경로와 decrypt 경로는 비대칭적이며, 특히 decrypt에서는 인증 태그 검증이 복호화보다 먼저 수행됩니다. 이 섹션에서는 커널의 authenc 구현을 기준으로 AEAD의 상태 전이를 분석합니다.

Encrypt 경로 상태 전이

AEAD 상태 머신: encrypt vs decrypt Encrypt 경로 1. 키 분리 (enc_key + auth_key) 2. IV 생성/설정 3. skcipher_encrypt(평문) 4. HMAC(AAD + 암호문) 계산 5. 태그 append → 완료 Decrypt 경로 1. 키 분리 (enc_key + auth_key) 2. HMAC(AAD + 암호문) 재계산 3. crypto_memneq(태그 비교) 상수 시간 비교 -EBADMSG 일치! 4. skcipher_decrypt 5. 평문 반환 불일치 일치 핵심 차이: Encrypt는 encrypt→MAC 순서, Decrypt는 MAC 검증→decrypt 순서 (Encrypt-then-MAC)
AEAD 상태 머신: encrypt(좌)는 암호화 후 MAC 계산, decrypt(우)는 MAC 검증 후 복호화

authenc 내부 구현 분석

커널의 crypto/authenc.c는 가장 기본적인 AEAD 구현으로, Encrypt-then-MAC 방식을 사용합니다. IPsec ESP에서 authenc(hmac(sha256),cbc(aes)) 형태로 주로 사용됩니다:

/* crypto/authenc.c — authenc encrypt 핵심 경로 (간략화) */

static int crypto_authenc_encrypt(struct aead_request *req)
{
    struct crypto_aead *authenc = crypto_aead_reqtfm(req);
    struct authenc_instance_ctx *ictx = aead_instance_ctx(
        aead_alg_instance(authenc));
    struct authenc_request_ctx *areq_ctx = aead_request_ctx(req);
    struct crypto_skcipher *enc = ictx->enc;
    int err;

    /* 1단계: skcipher로 평문 암호화 */
    struct skcipher_request *subreq = &areq_ctx->subreq;
    skcipher_request_set_tfm(subreq, enc);
    skcipher_request_set_crypt(subreq, req->src, req->dst,
                              req->cryptlen, req->iv);
    skcipher_request_set_callback(subreq, aead_request_flags(req),
                                 authenc_encrypt_done, req);

    err = crypto_skcipher_encrypt(subreq);
    if (err)
        return err;

    /* 2단계: AAD + 암호문에 대해 HMAC 계산 */
    return crypto_authenc_genicv(req, aead_request_flags(req));
}

/* authenc decrypt: MAC 검증이 먼저! */
static int crypto_authenc_decrypt(struct aead_request *req)
{
    struct crypto_aead *authenc = crypto_aead_reqtfm(req);
    unsigned int authsize = crypto_aead_authsize(authenc);
    int err;

    /* 1단계: AAD + 암호문에 대해 HMAC 재계산 */
    err = crypto_authenc_verify(req);
    if (err)
        return err;  /* -EBADMSG: 태그 불일치 → 복호화 거부 */

    /* 2단계: 태그 일치 확인 후에만 복호화 수행 */
    return crypto_authenc_decrypt_tail(req,
        aead_request_flags(req));
}

/* 태그 비교: 반드시 상수 시간(constant-time) 비교 사용 */
static int crypto_authenc_verify(struct aead_request *req)
{
    /* ... HMAC 계산 ... */

    /* crypto_memneq: 타이밍 부채널 방지 상수 시간 비교
     * memcmp와 달리 첫 불일치에서 조기 종료하지 않음 */
    if (crypto_memneq(computed_tag, received_tag, authsize))
        return -EBADMSG;

    return 0;
}

AEAD scatterlist 메모리 레이아웃

AEAD 요청의 scatterlist 레이아웃은 encrypt와 decrypt에서 비대칭적입니다. 이 차이를 정확히 이해하지 못하면 데이터 손상이나 인증 실패가 발생합니다:

방향req->src 레이아웃req->dst 레이아웃req->cryptlen 의미
Encrypt [AAD | 평문] [AAD | 암호문 | 태그] 평문 길이
Decrypt [AAD | 암호문 | 태그] [AAD | 평문] 암호문 + 태그 길이
/* AEAD scatterlist 구성 예제 (IPsec ESP 기준) */

struct scatterlist sg[3];
unsigned int authsize = crypto_aead_authsize(aead);

/* === Encrypt 시 === */
/* src: [AAD(assoclen) | 평문(cryptlen)]
 * dst: [AAD(assoclen) | 암호문(cryptlen) | 태그(authsize)] */
sg_init_table(sg, 3);
sg_set_buf(&sg[0], aad, assoclen);
sg_set_buf(&sg[1], plaintext, plaintext_len);
sg_set_buf(&sg[2], tag_space, authsize);  /* 태그 공간 확보 */

aead_request_set_crypt(req, sg, sg, plaintext_len, iv);
aead_request_set_ad(req, assoclen);

/* === Decrypt 시 === */
/* src: [AAD(assoclen) | 암호문+태그(cryptlen)]
 * cryptlen = 암호문 길이 + authsize
 * 실제 평문 길이 = cryptlen - authsize */
aead_request_set_crypt(req, sg, sg,
                      ciphertext_len + authsize,  /* 태그 포함! */
                      iv);
aead_request_set_ad(req, assoclen);
코드 설명

AEAD(Authenticated Encryption with Associated Data) 연산에서 scatterlist를 구성하는 IPsec ESP 기준 예제입니다. Encrypt와 Decrypt의 비대칭 레이아웃이 핵심입니다.

  • sg_init_table() / sg_set_buf()scatterlist 배열을 초기화하고 각 엔트리에 AAD, 평문/암호문, 태그 버퍼를 매핑합니다. include/linux/scatterlist.h에 정의된 API입니다.
  • Encrypt: sg[2] = tag_space암호화 시 출력에 인증 태그가 추가되므로, 태그 크기(crypto_aead_authsize())만큼의 추가 공간을 scatterlist에 미리 확보해야 합니다.
  • aead_request_set_crypt(req, sg, sg, ...)src와 dst를 같은 sg로 지정하면 in-place 처리가 됩니다. Encrypt의 cryptlen은 평문 길이, Decrypt의 cryptlen은 암호문+태그 길이로 의미가 다릅니다.
  • Decrypt: ciphertext_len + authsize복호화 시 cryptlen에 인증 태그 크기를 반드시 포함해야 합니다. 누락하면 태그가 잘려서 인증 검증이 무의미해집니다.
  • aead_request_set_ad(req, assoclen)AAD(Additional Authenticated Data) 길이를 지정합니다. 이 길이만큼의 scatterlist 앞부분은 인증에만 포함되고 암·복호화 대상에서는 제외됩니다.
AEAD 구현 시 흔한 실수:
  • decrypt의 cryptlen에 authsize 미포함 — 태그가 입력에서 잘리고 검증이 무의미해짐
  • Decrypt 시 MAC 검증 전에 복호화 수행 — 변조된 암호문의 평문이 노출되는 보안 취약점(Vulnerability)
  • memcmp로 태그 비교 — 타이밍 부채널 공격에 취약. 반드시 crypto_memneq() 사용
  • in-place 처리 시 src/dst 오프셋(Offset) 혼동 — AAD 영역은 변경되지 않지만 offset 계산에 포함

AES-NI 커널 통합 메커니즘

AES-NI 하드웨어 가속의 커널 통합은 단순히 AESENC 명령어를 호출하는 것 이상의 복잡한 시스템 엔지니어링을 포함합니다. kernel_fpu_begin()/end() 기반의 FPU 컨텍스트 관리, SIMD 래퍼를 통한 인터럽트 안전성 보장, S/W fallback 전환 등 핵심 메커니즘을 분석합니다.

kernel_fpu_begin/end 내부 동작

kernel_fpu_begin/end 동작 흐름 Process Context (프로세스 문맥) kernel_fpu_begin() preempt_disable() FPU 상태 저장 (XSAVE) task→thread.fpu.state SIMD 명령어 사용 가능 AESENC, PCLMULQDQ... kernel_fpu_end() → XRSTOR + preempt_enable() Softirq / Hardirq Context (인터럽트 문맥) kernel_fpu_begin() 불가! irq_fpu_usable() == false SIMD 래퍼 동작 cryptd kthread로 위임 kthread에서 FPU 사용 process context → 안전
Process context에서는 직접 FPU 사용, 인터럽트 context에서는 cryptd kthread로 위임
/* arch/x86/kernel/fpu/core.c — kernel_fpu_begin() 내부 */

void kernel_fpu_begin_mask(unsigned int kfpu_mask)
{
    /* 1. preemption 비활성화
     * FPU 상태가 복원되기 전에 다른 태스크로 전환되면
     * FPU 레지스터가 오염되므로 반드시 비활성화 */
    preempt_disable();

    /* 2. 현재 태스크의 FPU 상태를 메모리에 저장
     * XSAVE/XSAVEOPT/FXSAVE 중 CPU가 지원하는 최적 명령어 사용 */
    fpregs_lock();
    fpu__save(current);

    /* 3. 커널 FPU 사용 플래그 설정 */
    current->flags |= PF_KTHREAD_FPU;

    /* 이후 AES-NI 등 SIMD 명령어 사용 가능 */
}

void kernel_fpu_end(void)
{
    /* 1. 태스크의 FPU 상태 복원 (XRSTOR 등) */
    fpregs_unlock();

    /* 2. preemption 재활성화 */
    preempt_enable();
}

/* FPU 사용 가능 여부 확인 */
bool irq_fpu_usable(void)
{
    /* hardirq 또는 softirq 컨텍스트에서는 false
     * 이미 kernel_fpu_begin() 호출 중이면 false (중첩 금지) */
    if (in_irq() || in_softirq())
        return false;
    if (current->flags & PF_KTHREAD_FPU)
        return false;
    return true;
}

SIMD 래퍼와 cryptd 위임

커널의 SIMD 래퍼(crypto/simd.c)는 실행 컨텍스트에 따라 자동으로 처리 경로를 분기합니다:

/* crypto/simd.c — SIMD 래퍼의 encrypt 함수 (간략화) */

static int simd_skcipher_encrypt(struct skcipher_request *req)
{
    struct simd_skcipher_ctx *ctx = ...;

    /* crypto_simd_usable(): irq_fpu_usable()의 래퍼 */
    if (crypto_simd_usable()) {
        /* Process context: 직접 SIMD 알고리즘 호출
         * kernel_fpu_begin/end는 내부 알고리즘이 처리 */
        return crypto_skcipher_encrypt(ctx->internal_req);
    }

    /* Interrupt context: cryptd kthread로 위임
     * → cryptd 워커가 process context에서 실행
     * → 완료 시 콜백으로 결과 통보 */
    return cryptd_skcipher_enqueue(ctx->cryptd_req, req);
}

/* cryptd 워커 스레드 (crypto/cryptd.c) */
/* kcryptd 커널 스레드는 항상 process context에서 실행되므로
 * kernel_fpu_begin() 호출이 안전함 */
static void cryptd_skcipher_encrypt(struct crypto_async_request *base,
                                     int err)
{
    /* process context → kernel_fpu_begin() 가능 */
    err = crypto_skcipher_encrypt(subreq);
    /* 원본 요청의 완료 콜백 호출 */
    req->base.complete(&req->base, err);
}

S/W Fallback 메커니즘

H/W 가속기가 특정 조건(키 크기, 입력 크기, FPU 사용 불가 등)을 처리하지 못할 때 자동으로 S/W 구현으로 전환하는 메커니즘입니다:

/* H/W 가속 알고리즘의 fallback 구현 패턴 */

struct my_hw_ctx {
    struct crypto_skcipher *fallback;  /* S/W fallback tfm */
    /* ... H/W 고유 컨텍스트 ... */
};

static int my_hw_init_tfm(struct crypto_skcipher *tfm)
{
    struct my_hw_ctx *ctx = crypto_skcipher_ctx(tfm);
    const char *name = crypto_skcipher_alg_name(tfm);

    /* CRYPTO_ALG_NEED_FALLBACK으로 S/W 전용 구현을 요청
     * → priority가 낮은 generic 구현이 선택됨 */
    ctx->fallback = crypto_alloc_skcipher(name, 0,
                        CRYPTO_ALG_NEED_FALLBACK);
    if (IS_ERR(ctx->fallback))
        return PTR_ERR(ctx->fallback);

    /* fallback의 reqsize를 반영 */
    crypto_skcipher_set_reqsize(tfm,
        max(crypto_skcipher_reqsize(ctx->fallback),
            sizeof(struct my_hw_subreq)));

    return 0;
}

static int my_hw_encrypt(struct skcipher_request *req)
{
    struct my_hw_ctx *ctx = crypto_skcipher_ctx(
        crypto_skcipher_reqtfm(req));

    /* 조건 확인: SIMD 사용 가능 + H/W 지원 범위 */
    if (!crypto_simd_usable() ||
        req->cryptlen < MY_HW_MIN_SIZE) {
        /* Fallback: S/W 구현으로 전환 */
        skcipher_request_set_tfm(req, ctx->fallback);
        return crypto_skcipher_encrypt(req);
    }

    /* H/W 가속 경로 */
    kernel_fpu_begin();
    /* ... AES-NI 명령어 사용 ... */
    kernel_fpu_end();
    return 0;
}

/* 알고리즘 등록 시 NEED_FALLBACK 플래그 설정 */
static struct skcipher_alg my_hw_alg = {
    .base.cra_name        = "xts(aes)",
    .base.cra_driver_name = "xts-aes-my-hw",
    .base.cra_priority    = 500,
    .base.cra_flags       = CRYPTO_ALG_ASYNC |
                            CRYPTO_ALG_NEED_FALLBACK,
    /* ... */
};
코드 설명

H/W 가속 알고리즘이 S/W fallback을 내장하는 전형적 패턴으로, arch/x86/crypto/aesni-intel_glue.c 등 실제 드라이버에서 널리 사용됩니다.

  • my_hw_ctx.fallbacktfm 초기화 시 같은 알고리즘의 S/W 전용 구현을 crypto_alloc_skcipher()로 확보합니다. CRYPTO_ALG_NEED_FALLBACK mask를 사용하여 H/W 구현이 아닌 generic 구현이 선택되도록 합니다.
  • crypto_skcipher_set_reqsize()fallback tfm의 reqsize와 H/W 서브요청 크기 중 큰 값을 설정하여, 요청 구조체에 두 경로 모두 충분한 컨텍스트 공간이 확보되도록 합니다.
  • crypto_simd_usable() 검사인터럽트 컨텍스트에서는 FPU/SIMD 레지스터를 사용할 수 없으므로, 이 조건이 false이면 S/W fallback으로 전환합니다. 데이터 크기가 H/W 최소 처리 단위보다 작은 경우에도 fallback을 사용합니다.
  • skcipher_request_set_tfm(req, ctx->fallback)원본 요청의 tfm을 fallback tfm으로 교체한 뒤 crypto_skcipher_encrypt()를 호출하여 S/W 경로로 투명하게 전환합니다.
  • CRYPTO_ALG_NEED_FALLBACK 플래그알고리즘 등록 시 이 플래그를 설정하면 커널이 동일 cra_name의 S/W 구현 존재를 보장하며, fallback 할당 실패를 방지합니다.
ℹ️

Fallback 성능 영향: Fallback은 tfm 전환 오버헤드가 있지만, 일반적으로 작은 블록(16~64바이트)에서만 발생합니다. 대부분의 워크로드(dm-crypt, IPsec)는 킬로바이트 단위 데이터를 처리하므로 H/W 경로를 사용합니다. perf stat으로 aesni_* vs aes_generic_* 호출 비율을 확인하여 fallback 빈도를 모니터링할 수 있습니다.

암호화 성능 추적과 벤치마크

커널 암호화 작업의 성능 분석에는 tcrypt 모듈 벤치마크, ftrace 함수 추적, bpftrace 동적 계측의 세 가지 기법이 주로 사용됩니다. 이 섹션에서는 각 도구의 실전 활용법을 다룹니다.

tcrypt 고급 벤치마크

tcrypt 모듈은 커널 내부에서 직접 Crypto Framework (Crypto API)를 호출하여 순수 암호 연산 성능을 측정합니다. I/O 스택 오버헤드 없이 알고리즘 자체의 throughput을 확인할 수 있습니다:

# ═══ tcrypt 벤치마크 실전 활용 ═══

# skcipher 벤치마크 (AES-CBC, AES-CTR, AES-XTS)
modprobe tcrypt mode=500 sec=3   # 3초간 측정
modprobe tcrypt mode=501 sec=3   # AES-CTR
modprobe tcrypt mode=502 sec=3   # AES-XTS

# AEAD 벤치마크 (AES-GCM, ChaCha20-Poly1305)
modprobe tcrypt mode=211 sec=3   # AES-GCM

# 해시 벤치마크
modprobe tcrypt mode=403 sec=3   # SHA-256
modprobe tcrypt mode=404 sec=3   # SHA-512

# 결과 파싱: ops/sec와 throughput 추출
dmesg | grep "testing speed" -A 20 | tail -25
# testing speed of async cbc(aes) (cbc-aes-aesni) encryption
# test 5 (256 bit key, 8192 byte blocks): 495230 ops in 3 sec
#   → 495230 × 8192 / 3 = ~1.35 GB/s

# S/W vs H/W 비교: aes_generic 강제 사용
# 1. aesni_intel 모듈 일시 언로드
modprobe -r aesni_intel 2>/dev/null
modprobe tcrypt mode=500 sec=3   # aes_generic으로 테스트
# 2. aesni_intel 재로드
modprobe aesni_intel
modprobe tcrypt mode=500 sec=3   # AES-NI로 테스트
# → throughput 비교로 가속 비율 확인

ftrace를 이용한 암호화 함수 추적

ftrace는 커널 내장 추적기로, 암호화 함수의 호출 빈도와 지연 시간을 분석할 수 있습니다:

# ═══ ftrace로 crypto 함수 추적 ═══

# 1. 추적 가능한 crypto 함수 목록 확인
grep crypto /sys/kernel/debug/tracing/available_filter_functions | head -20
# crypto_skcipher_encrypt
# crypto_skcipher_decrypt
# crypto_aead_encrypt
# crypto_shash_digest
# crypto_alloc_tfm

# 2. function_graph 추적기로 호출 흐름 확인
cd /sys/kernel/debug/tracing
echo function_graph > current_tracer
echo crypto_skcipher_encrypt > set_graph_function
echo 1 > tracing_on

# dm-crypt I/O 발생 (다른 터미널에서)
dd if=/dev/urandom of=/dev/mapper/my-crypt bs=4K count=100

echo 0 > tracing_on
cat trace | head -40
# kcryptd/0-1234 |   0.456 us  | crypto_skcipher_encrypt();
#               |              |   cbc_encrypt();
#               |              |     kernel_fpu_begin();
#               |   0.312 us  |     aesni_cbc_enc();
#               |              |     kernel_fpu_end();

# 3. 함수 호출 히스토그램 (지연 시간 분포)
echo 0 > tracing_on
echo nop > current_tracer
echo crypto_skcipher_encrypt > set_ftrace_filter
echo 1 > function_profile_enabled
echo 1 > tracing_on

# 워크로드 실행 후
cat trace_stat/function0 | grep crypto
#   Function              Hit    Time          Avg
#   --------              ---    ----          ---
#   crypto_skcipher_encrypt  152847  45.230 ms   0.296 us

# 4. 추적 해제
echo 0 > tracing_on
echo 0 > function_profile_enabled
echo nop > current_tracer
echo > set_ftrace_filter

bpftrace를 이용한 동적 계측

bpftrace는 eBPF 기반의 고급 추적 도구로, 커널 암호화 작업의 세부 통계를 실시간(Real-time)으로 수집할 수 있습니다:

# ═══ bpftrace로 crypto 연산 계측 ═══

# 1. 암호화 함수 호출 빈도 (10초간 수집)
bpftrace -e '
kprobe:crypto_skcipher_encrypt,
kprobe:crypto_skcipher_decrypt,
kprobe:crypto_aead_encrypt,
kprobe:crypto_aead_decrypt,
kprobe:crypto_shash_digest {
    @calls[probe] = count();
}
interval:s:10 { exit(); }
'
# @calls[kprobe:crypto_skcipher_encrypt]: 284713
# @calls[kprobe:crypto_aead_encrypt]: 15234
# @calls[kprobe:crypto_shash_digest]: 42891

# 2. 암호화 함수별 지연 시간 히스토그램
bpftrace -e '
kprobe:crypto_skcipher_encrypt { @start[tid] = nsecs; }
kretprobe:crypto_skcipher_encrypt /@start[tid]/ {
    @latency_ns = hist(nsecs - @start[tid]);
    delete(@start[tid]);
}
interval:s:10 { exit(); }
'
# @latency_ns:
# [128, 256)       12834 |@@@@@@@@@@@@@@@@@@@@|
# [256, 512)       85219 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
# [512, 1K)        42103 |@@@@@@@@@@@@@@@@@@@|
# [1K, 2K)          5847 |@@|
# [2K, 4K)           312 |

# 3. 프로세스별 crypto 사용량 추적
bpftrace -e '
kprobe:crypto_skcipher_encrypt {
    @by_comm[comm] = count();
}
interval:s:10 { exit(); }
'
# @by_comm[kcryptd/0]: 198234
# @by_comm[ksoftirqd/2]: 45123
# @by_comm[openvpn]: 12345

# 4. kernel_fpu_begin/end 구간 측정 (FPU 점유 시간)
bpftrace -e '
kprobe:kernel_fpu_begin_mask { @fpu_start[tid] = nsecs; }
kprobe:kernel_fpu_end /@fpu_start[tid]/ {
    @fpu_hold_ns = hist(nsecs - @fpu_start[tid]);
    delete(@fpu_start[tid]);
}
interval:s:5 { exit(); }
'
# → FPU 점유 시간이 너무 길면 preemption latency 문제 가능
암호화 성능 추적 도구 비교 tcrypt 순수 throughput 측정 커널 모듈, 오프라인 벤치 I/O 스택 오버헤드 없음 알고리즘 비교, 회귀 테스트 ftrace 함수 호출 흐름 추적 커널 내장, 설정 간편 function_graph, 히스토그램 호출 경로, 병목 지점 파악 bpftrace 동적 통계 수집 eBPF 기반, 프로덕션 안전 프로세스별, 히스토그램, 조건부 실시간 모니터링, 이상 탐지 권장 사용 시나리오 알고리즘 선택/비교 병목 함수 식별 프로덕션 모니터링 tcrypt(오프라인) → ftrace(개발/디버깅) → bpftrace(운영 환경)
성능 추적 3단계: tcrypt(순수 벤치마크) → ftrace(호출 분석) → bpftrace(운영 계측)

perf를 이용한 crypto 프로파일링(Profiling)

# ═══ perf로 crypto 핫스팟 분석 ═══

# 1. dm-crypt I/O 중 CPU 프로파일
perf record -g -p $(pgrep -f kcryptd) -- sleep 10
perf report --no-children

# 주요 확인 포인트:
#   aesni_xts_encrypt    → AES-NI 사용 확인
#   aes_encrypt          → S/W fallback 사용 (성능 문제)
#   kernel_fpu_begin     → FPU 컨텍스트 스위칭 비용
#   scatterwalk_*        → scatterlist 순회 오버헤드

# 2. 특정 crypto 함수의 캐시 미스 분석
perf stat -e cache-misses,cache-references,instructions \
  -p $(pgrep -f kcryptd) -- sleep 5

# 3. IPsec crypto 성능 분석
perf top -e cycles -g --no-children \
  --call-graph dwarf -- -p $(pgrep -f "pluto\|charon")

# 4. CPU 명령어별 통계 (AES-NI 파이프라인 효율)
perf stat -e cycles,instructions,r01b1,r02b1 \
  -- modprobe tcrypt mode=500 sec=2
# r01b1: UOPS_EXECUTED.THREAD (마이크로-ops 실행 수)
# → IPC(Instructions Per Cycle)로 파이프라인 효율 확인
💡

성능 추적 실전 팁: (1) tcrypt는 모듈 로드 시 즉시 테스트를 수행하고 완료 후 -EAGAIN을 반환하므로 rmmod tcrypt로 정리 불필요, (2) ftrace의 function_graph 추적은 오버헤드가 크므로 프로덕션에서는 짧은 시간만 사용, (3) bpftrace는 오버헤드가 매우 낮아(<1%) 프로덕션 서버에서도 안전하게 사용 가능, (4) 벤치마크 시 터보 부스트(cpupower frequency-set -g performance)와 NUMA 바인딩(numactl --cpunodebind=0)으로 측정 변동성을 줄이세요.

ARM 암호화 가속

ARMv8-A 아키텍처는 Crypto Extensions(CE)를 통해 AES, SHA, 다항식 곱셈 등의 암호 연산을 하드웨어에서 직접 수행합니다. x86의 AES-NI에 대응하는 기능으로, 모바일 SoC부터 서버용 ARM 프로세서까지 널리 지원됩니다. 커널 Crypto API는 CE 명령어를 자동 감지하여 소프트웨어 구현보다 높은 우선순위로 등록합니다.

ARMv8-A Crypto Extensions 명령어

ARM CE는 AES, SHA-1, SHA-256, 다항식 곱셈(GCM용)을 위한 전용 명령어를 제공합니다:

명령어동작설명
AESEAES 단일 라운드 암호화SubBytes + ShiftRows (AddRoundKey 별도)
AESDAES 단일 라운드 복호화InvSubBytes + InvShiftRows
AESMCAES MixColumns암호화 라운드의 열 혼합 단계
AESIMCAES 역 MixColumns복호화 라운드의 역 열 혼합
SHA1C/P/MSHA-1 라운드Choose/Parity/Majority 함수별 4라운드 처리
SHA1HSHA-1 해시 고정 회전SHA-1 스케줄링 회전 연산
SHA1SU0/1SHA-1 스케줄 업데이트메시지 스케줄 확장
SHA256H/H2SHA-256 라운드SHA-256 압축 함수 4라운드 처리
SHA256SU0/1SHA-256 스케줄 업데이트메시지 스케줄 확장
PMULL/PMULL2다항식 곱셈64×64→128비트 캐리리스 곱셈 (GHASH용)
ℹ️

x86 AES-NI와의 차이: AES-NI의 AESENC는 SubBytes+ShiftRows+MixColumns+AddRoundKey를 한 번에 수행하지만, ARM CE의 AESE는 SubBytes+ShiftRows만 처리하고 AESMC와 XOR을 별도로 수행합니다. 그러나 ARM 파이프라인에서 AESE+AESMC는 매크로 퓨전되어 실질적으로 단일 사이클에 처리됩니다.

커널 암호 모듈

ARM64 커널은 CE, NEON, 제네릭 세 가지 계층의 암호 모듈을 제공합니다:

모듈Kconfig알고리즘Priority요구사항
aes-arm64-ceCRYPTO_AES_ARM64_CE_BLKecb/cbc/ctr/xts(aes)300ARM CE
sha256-arm64-ceCRYPTO_SHA256_ARM64_CEsha256, sha224300ARM CE
sha512-arm64-ceCRYPTO_SHA512_ARM64_CEsha512, sha384300ARM CE
ghash-ceCRYPTO_GHASH_ARM64_CEghash300PMULL
aes-arm64-neon-blkCRYPTO_AES_ARM64_NEON_BLKecb/cbc/ctr/xts(aes)200NEON/ASIMD
sha256-arm64CRYPTO_SHA256_ARM64sha256150NEON
aes-genericCRYPTO_AESaes100없음

모듈 등록과 priority 선택

ARM CE 모듈은 module_cpu_feature_match() 매크로를 사용하여 CPU가 해당 기능을 지원할 때만 자동 로드됩니다:

/* arch/arm64/crypto/aes-ce-core.S + aes-glue.c 패턴 */

/* CE 모듈: priority 300 — CPU에 CE 기능이 있으면 자동 선택 */
static struct skcipher_alg aes_algs[] = {{
    .base.cra_name        = "cbc(aes)",
    .base.cra_driver_name = "cbc-aes-ce",
    .base.cra_priority    = 300,
    .setkey               = ce_aes_setkey,
    .encrypt              = ce_aes_cbc_encrypt,
    .decrypt              = ce_aes_cbc_decrypt,
}};

/* 모듈 로드 조건: ARM64_HAS_AES feature bit 확인 */
module_cpu_feature_match(AES, aes_ce_mod_init);

/* NEON 모듈: priority 200 — CE 없는 ARMv8에서 사용 */
static struct skcipher_alg aes_neon_algs[] = {{
    .base.cra_name        = "cbc(aes)",
    .base.cra_driver_name = "cbc-aes-neon",
    .base.cra_priority    = 200,
}};

/* 제네릭 모듈: priority 100 — 항상 사용 가능한 최후의 수단 */
/* crypto/aes_generic.c */
# ARM64에서 Crypto Extensions 지원 확인
cat /proc/cpuinfo | grep -i features
# Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics ...
#            ^^^ ^^^^^ ^^^^ ^^^^         — CE 관련 플래그

# 등록된 AES 드라이버와 priority 확인
grep -A4 'cbc(aes)' /proc/crypto
# name         : cbc(aes)
# driver       : cbc-aes-ce        ← CE 모듈 (priority 300)
# priority     : 300

NEON 기반 AES (bit-slicing)

CE가 없는 ARMv7이나 초기 ARMv8 프로세서에서는 NEON(ASIMD) SIMD 레지스터를 활용한 bit-slicing 기법으로 AES를 가속합니다. S-Box 테이블 룩업 대신 비트 연산으로 AES 라운드를 구현하여, 타이밍 부채널을 방지하면서 제네릭 구현보다 2~4배 빠른 성능을 제공합니다:

/* arch/arm64/crypto/aes-neonbs-glue.c — bit-sliced AES */
/* 8블록을 병렬로 처리하여 NEON 128-bit 레지스터 활용 극대화 */
static int aesbs_cbc_encrypt(struct skcipher_request *req)
{
    /* NEON 레지스터 사용을 위해 커널 FPSIMD 컨텍스트 획득 */
    kernel_neon_begin();
    /* bit-slicing: 8개 AES 블록을 8개 NEON 레지스터에 전치하여
     * 각 비트 위치별로 병렬 S-Box 연산 수행 */
    aesbs_cbc_encrypt_neon(walk.dst.virt.addr,
                           walk.src.virt.addr, ctx->rk, nbytes, walk.iv);
    kernel_neon_end();
}

SVE2 암호화 확장

ARMv9의 SVE2(Scalable Vector Extension 2)는 가변 길이 벡터 레지스터(128~2048비트)에서 동작하는 암호화 명령어를 추가합니다:

명령어기능비고
SM4E / SM4EKEYSM4 암호화/키 확장중국 국가 표준 블록 암호
AESE / AESMC (SVE2)벡터 길이만큼 AES 병렬 처리256비트 SVE → 2블록 동시
RAX1SHA-3 rotate-and-XORKeccak 순열 가속
RSHAX / BEXT / BDEP비트 조작암호 알고리즘 보조
💡

SVE2 커널 지원 현황: Linux 6.x 기준 SVE2 crypto 명령어의 커널 Crypto API 드라이버는 아직 개발 초기 단계입니다. SM4 SVE2 드라이버(sm4-ce-core.S)가 먼저 합류했으며, AES/SHA-3 SVE2 최적화는 향후 커널에서 지원될 예정입니다. 벡터 길이가 가변이므로 기존 CE 고정 128비트 대비 처리량 확장이 가능합니다.

ARM CE 성능 비교

Cortex-A76 (ARMv8.2) 기준 tcrypt 벤치마크 결과:

알고리즘구현1024B 처리량8192B 처리량가속 배율
AES-256-CBCaes-generic~120 MB/s~130 MB/s1.0×
aes-neon (bit-slice)~450 MB/s~520 MB/s~4.0×
aes-ce~1.8 GB/s~2.2 GB/s~17×
SHA-256sha256-generic~95 MB/s~100 MB/s1.0×
sha256-ce~850 MB/s~1.1 GB/s~11×
AES-256-GCMgcm(aes-generic)~80 MB/s~90 MB/s1.0×
gcm(aes-ce) + ghash-ce~1.5 GB/s~1.9 GB/s~21×
ARM64 Crypto 모듈 Priority 선택 흐름 crypto_alloc_skcipher("cbc(aes)") CE feature available? Yes cbc-aes-ce priority 300 No NEON available? Yes cbc-aes-neon priority 200 No aes-generic priority 100
Crypto API는 등록된 구현 중 priority가 가장 높은 모듈을 자동 선택합니다

x86 추가 암호화 명령어

AES-NI 외에도 x86 아키텍처는 SHA, CRC32, SM3/SM4 등의 전용 명령어를 세대별로 추가해왔습니다. 커널은 각 명령어 세트에 대응하는 최적화 모듈을 제공하며, CPUID 플래그 기반으로 자동 선택합니다.

SHA-NI (SHA Extensions)

SHA-NI는 SHA-1과 SHA-256 라운드 함수를 하드웨어에서 수행하는 명령어 세트입니다:

CPU도입 시기SHA-NI 명령어커널 모듈Priority
Intel Goldmont (Atom)2016SHA1RNDS4, SHA1NEXTE, SHA256RNDS2, SHA256MSG1/2sha256-ni300
Intel Ice Lake2019동일sha256-ni300
AMD Zen (Ryzen/EPYC)2017동일sha256-ni300
# SHA-NI 지원 확인
grep sha_ni /proc/cpuinfo
# flags : ... sha_ni ...

# SHA-256 드라이버 확인: sha256-ni가 가장 높은 priority
grep -A3 'sha256' /proc/crypto | grep -E 'name|driver|priority'
# name         : sha256
# driver       : sha256-ni         ← SHA-NI 하드웨어 가속
# priority     : 300

# tcrypt 벤치마크: SHA-256
modprobe tcrypt mode=403 sec=3
# SHA-NI: ~2.5 GB/s (8K blocks) vs SW: ~300 MB/s → 약 8배 가속

AVX/AVX2 최적화 해시

SHA-NI가 없는 CPU에서는 AVX/AVX2 SIMD 명령어를 활용한 최적화 해시 구현을 사용합니다. Multi-buffer 기법은 여러 독립적인 해시 연산을 SIMD 레인에 배치하여 병렬 처리합니다:

모듈명령어 세트Priority특징
sha256-ssse3SSSE3150기본 SIMD 최적화
sha256-avxAVX160256비트 레지스터 활용
sha256-avx2AVX2170multi-buffer 4-way 병렬
sha512-avx2AVX2170SHA-512 multi-buffer
sha256-niSHA-NI300전용 하드웨어 명령어
/* arch/x86/crypto/sha256_avx2_asm.S 개념 — multi-buffer 기법 */
/* 4개의 독립적인 SHA-256 연산을 YMM 레지스터에서 병렬 처리 */
/*
 * YMM0 = [msg_A[0:31] | msg_B[0:31] | msg_C[0:31] | msg_D[0:31]]
 * 각 64바이트 레인에서 독립적인 SHA-256 라운드 수행
 * → 단일 스트림 대비 2~3배 처리량 향상
 */

/* 커널 등록 예시 */
static struct shash_alg sha256_avx2_alg = {
    .digestsize   = SHA256_DIGEST_SIZE,
    .init         = sha256_base_init,
    .update       = sha256_avx2_update,
    .final        = sha256_avx2_final,
    .base = {
        .cra_name        = "sha256",
        .cra_driver_name = "sha256-avx2",
        .cra_priority    = 170,
    },
};

SM3/SM4 명령어 (Sapphire Rapids)

Intel Sapphire Rapids(4세대 Xeon)부터 중국 국가 표준 암호 알고리즘인 SM3(해시)과 SM4(블록 암호)의 전용 명령어를 지원합니다. 커널 모듈 sm4-aesni-avx2sm3-avx가 이를 활용합니다. SM4는 국제 표준 ISO/IEC 18033-3:2010에도 등록되어 있으며, 중국 시장 제품에서 AES 대신 필수적으로 사용됩니다.

CRC32C SSE4.2 가속

SSE4.2의 CRC32 명령어는 Castagnoli CRC(CRC32C)를 하드웨어에서 수행합니다. ext4, btrfs, iSCSI 등 커널 전반에서 데이터 무결성 검증에 사용됩니다:

# CRC32C 가속 모듈 확인
grep crc32 /proc/crypto
# name         : crc32c
# driver       : crc32c-intel       ← SSE4.2 하드웨어 가속
# priority     : 200

# ext4 파일시스템에서 CRC32C 사용 확인
dmesg | grep crc32c
# ext4: using crc32c-intel for CRC32C checksums

# 성능: crc32c-intel ~20 GB/s vs crc32c-generic ~800 MB/s
# → SSE4.2 CRC32 명령어는 클럭당 8바이트 처리 가능
ℹ️

Priority 스택 요약: x86에서 동일 알고리즘에 여러 구현이 존재할 때 선택 순서는 전용 명령어(300~400) > AVX2(170) > AVX(160) > SSSE3(150) > Generic(100)입니다. /proc/crypto에서 동일 name에 대해 driver와 priority를 확인하여 현재 시스템에서 어떤 구현이 활성화되어 있는지 파악할 수 있습니다.

x86 Crypto 구현 Priority 스택 AES AES-NI (400) AES-NI+AVX2 XTS (250) aes-generic (100) SHA-256 SHA-NI (300) sha256-avx2 (170) sha256-avx (160) sha256-ssse3 (150) sha256-generic (100) CRC32C crc32c-intel (200) crc32c-generic (100) Priority: 높을수록 우선 선택 — 전용 HW(300~400) > AVX2(170) > AVX(160) > SSSE3(150) > Generic(100)
x86 암호화 구현은 CPU 기능에 따라 계층적으로 등록되며, 가장 높은 priority가 자동 선택됩니다

네트워크 암호화 오프로드

네트워크 암호화를 NIC 하드웨어에 오프로드하면 CPU 부하를 대폭 줄이고 와이어 속도에 가까운 암호화 처리량을 달성할 수 있습니다. 커널은 IPsec, kTLS, MACsec 세 가지 프로토콜에서 NIC 암호화 오프로드를 지원하며, 각각 다른 오프로드 레벨과 드라이버 콜백을 제공합니다.

IPsec 인라인 오프로드

IPsec 오프로드는 xfrm_dev_offload 구조체를 통해 두 가지 모드를 지원합니다:

모드상수NIC 처리 범위CPU 역할
Crypto offloadXFRM_DEV_OFFLOAD_CRYPTOESP 암복호화만ESP 헤더 구성, 패킷 라우팅(Routing)
Packet offloadXFRM_DEV_OFFLOAD_PACKETESP 암복호화 + 헤더 삽입/제거SA/SP 정책만 관리

NIC 드라이버는 xfrmdev_ops 콜백을 구현하여 SA(Security Association)를 하드웨어에 설치합니다:

/* include/linux/netdevice.h — IPsec 오프로드 콜백 */
struct xfrmdev_ops {
    int  (*xdo_dev_state_add)(struct xfrm_state *x,
                              struct netlink_ext_ack *extack);
    void (*xdo_dev_state_delete)(struct xfrm_state *x);
    void (*xdo_dev_state_free)(struct xfrm_state *x);
    bool (*xdo_dev_offload_ok)(struct sk_buff *skb,
                               struct xfrm_state *x);
    int  (*xdo_dev_policy_add)(struct xfrm_policy *xp,
                               struct netlink_ext_ack *extack);
    void (*xdo_dev_policy_delete)(struct xfrm_policy *xp);
};

/* 지원 NIC 예시: Mellanox ConnectX-6 Dx+ (mlx5), Intel E810 (ice) */
💡

Crypto vs Packet offload 선택: Crypto offload는 SA만 NIC에 설치하므로 기존 라우팅/방화벽(Firewall) 규칙과 완전 호환됩니다. Packet offload는 ESP 헤더까지 NIC이 처리하여 CPU 부하가 더 낮지만, 라우팅/필터링이 ESP 내부 패킷에 접근할 수 없습니다. 대부분의 경우 crypto offload가 호환성과 성능의 균형이 좋습니다. 상세한 구현과 설정은 IPsec 하드웨어 오프로드를 참고하세요.

kTLS 암호화 오프로드

kTLS(Kernel TLS)는 TLS 레코드 계층의 암복호화를 NIC에 오프로드할 수 있습니다. 사용자 공간에서 TLS 핸드셰이크를 완료한 후 setsockopt(TCP_ULP, "tls")로 커널에 키를 전달하면, 지원 NIC이 자동으로 오프로드합니다:

NIC드라이버TX offloadRX offload지원 암호
Mellanox ConnectX-6+mlx5TLS 1.2/1.3TLS 1.2/1.3AES-128/256-GCM
Broadcom BCM57504bnxtTLS 1.2AES-128-GCM
Intel E810iceTLS 1.2/1.3TLS 1.2/1.3AES-128/256-GCM
ℹ️

kTLS 오프로드 조건: (1) NIC 드라이버가 tls_dev_add()/tls_dev_del() 콜백을 구현해야 하고, (2) ethtool -k eth0 | grep tls에서 tls-hw-tx-offload/tls-hw-rx-offload가 on이어야 합니다. 오프로드 불가 시 커널이 자동으로 소프트웨어 kTLS로 폴백합니다. 상세는 kTLS 하드웨어 오프로드를 참고하세요.

MACsec 오프로드

MACsec(IEEE 802.1AE)은 이더넷 프레임 레벨에서 AES-GCM-128/256 암호화를 수행합니다. 일부 NIC(Mellanox ConnectX-6 Dx, Intel E810, Marvell Prestera)에서 MACsec 오프로드를 지원하며, macsec_ops 콜백으로 SA를 하드웨어에 설치합니다. MACsec의 상세 아키텍처와 설정은 이더넷 MACsec을 참고하세요.

오프로드 확인 및 설정

# ━━━ 네트워크 암호화 오프로드 확인/설정 ━━━

# IPsec offload 기능 확인
ethtool -k eth0 | grep esp
# esp-hw-offload: on        ← IPsec crypto offload 가능
# esp-tx-csum-hw-offload: on

# IPsec SA에 오프로드 설정
ip xfrm state add src 10.0.0.1 dst 10.0.0.2 proto esp spi 0x100 \
    mode transport aead 'rfc4106(gcm(aes))' 0x... 128 \
    offload dev eth0 dir out     # ← offload 키워드

# 오프로드 상태 확인
ip xfrm state list | grep -A2 offload
#   offload dev eth0 dir out type crypto

# kTLS offload 확인
ethtool -k eth0 | grep tls
# tls-hw-tx-offload: on
# tls-hw-rx-offload: on

# MACsec offload 확인
ethtool -k eth0 | grep macsec
# macsec-hw-offload: on
네트워크 암호화 오프로드 경로 비교 Application (IPsec/TLS) SW 경로 Crypto offload Packet offload CPU: Crypto API (AES-NI / ARM CE) CPU: ESP 헤더 구성 CPU: ESP 헤더 구성 (암호화는 NIC에 위임) CPU: SA/SP 정책만 (모든 처리를 NIC에 위임) NIC: 패킷 전송만 NIC: 암복호화 수행 NIC: ESP 전체 처리 Wire (네트워크)
SW 경로는 CPU가 모든 처리를 담당하고, Crypto offload는 암복호화만, Packet offload는 ESP 전체를 NIC에 위임합니다

스토리지 인라인 암호화

스토리지 인라인 암호화(Inline Encryption)는 데이터가 저장장치에 기록/읽기될 때 전용 하드웨어 엔진(ICE, Inline Crypto Engine)이 와이어 속도로 실시간 암복호화를 수행하는 기술입니다. CPU 개입이 전혀 없으므로 dm-crypt 대비 성능 오버헤드가 없고, 모바일 기기의 파일 기반 암호화(FBE)에서 핵심 역할을 합니다.

blk-crypto 프레임워크

Linux 커널의 blk-crypto 프레임워크는 블록 레이어에서 인라인 암호화를 추상화합니다. 상위 계층(fscrypt, dm-crypt)이 blk_crypto_key를 설정하면, 하드웨어 지원 여부에 따라 ICE 또는 소프트웨어 폴백으로 자동 전환합니다:

/* include/linux/blk-crypto.h — 핵심 구조체 */
struct blk_crypto_key {
    struct blk_crypto_config crypto_cfg;  /* 암호 설정 */
    unsigned int data_unit_size;           /* 암호화 단위 (보통 4096) */
    unsigned int size;                      /* 키 크기 */
    u8 raw[BLK_CRYPTO_MAX_KEY_SIZE];        /* 원시 키 데이터 */
};

enum blk_crypto_mode_num {
    BLK_ENCRYPTION_MODE_INVALID,
    BLK_ENCRYPTION_MODE_AES_256_XTS,      /* FBE 기본 */
    BLK_ENCRYPTION_MODE_AES_128_CBC_ESSIV, /* 레거시 FDE */
    BLK_ENCRYPTION_MODE_ADIANTUM,          /* CE 없는 저가형 */
    BLK_ENCRYPTION_MODE_SM4_XTS,           /* 중국 표준 */
};

/* blk_crypto_profile: 스토리지 디바이스의 ICE 능력 기술 */
struct blk_crypto_profile {
    struct blk_crypto_ll_ops ll_ops;   /* keyslot 관리 콜백 */
    unsigned int max_dun_bytes_supported; /* DUN 크기 */
    unsigned int num_slots;               /* 하드웨어 keyslot 수 */
    unsigned int modes_supported[BLK_ENCRYPTION_MODE_MAX];
};
암호 모드키 크기용도비고
AES-256-XTS512비트 (2×256)Android FBE 기본대부분의 ICE가 지원
AES-128-CBC-ESSIV256비트레거시 FDEIV 예측 방지용 ESSIV
Adiantum256비트CE 없는 저가형 기기ChaCha 기반, SW 전용
SM4-XTS256비트중국 시장 규정SM4 블록 암호 + XTS

인라인 암호화 엔진

주요 스토리지 인터페이스별 인라인 암호화 엔진:

엔진스토리지커널 드라이버알고리즘keyslot 수
Qualcomm ICEUFSufs-qcom + qcom-iceAES-256-XTS보통 32개
Samsung FMPUFSufs-exynosAES-256-XTS8~16개
MediaTek CQHCIeMMCcqhciAES-256-XTS32개
NVMe (TCG Opal)NVMe SSDnvmeAES-256-XTS벤더 종속
ℹ️

교차참조: UFS ICE의 하드웨어 아키텍처, keyslot 관리, 지원 알고리즘 상세는 UFS 인라인 암호화를 참고하세요. eMMC CQHCI 암호화는 eMMC를 참고하세요.

소프트웨어 폴백

CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK이 활성화되면, ICE가 없는 디바이스에서도 blk-crypto 인터페이스를 사용할 수 있습니다. 폴백 모듈은 내부적으로 Crypto API의 crypto_skcipher를 사용하여 소프트웨어로 암복호화를 수행합니다:

/* block/blk-crypto-fallback.c — 소프트웨어 폴백 개요 */
static int blk_crypto_fallback_encrypt_bio(struct bio **bio_ptr)
{
    struct bio_crypt_ctx *bc = (*bio_ptr)->bi_crypt_context;
    struct crypto_skcipher *tfm;

    /* blk_crypto_mode → Crypto API 알고리즘 이름 매핑 */
    /* AES_256_XTS → "xts(aes)" */
    tfm = crypto_alloc_skcipher(
        blk_crypto_modes[bc->bc_key->crypto_cfg.crypto_mode].name,
        0, 0);

    /* Crypto API를 통한 소프트웨어 암호화 수행 */
    /* AES-NI/ARM CE가 있으면 HW 가속으로 폴백됨 */
    crypto_skcipher_encrypt(req);
}

fscrypt + blk-crypto 연동

fscrypt(파일시스템 암호화)는 blk-crypto를 통해 ICE를 활용합니다. 각 파일(또는 디렉토리)마다 고유한 키를 생성하고, blk-crypto에 키를 등록하여 I/O 요청에 첨부합니다:

# ━━━ fscrypt + inline encryption 설정 ━━━

# 1. 디바이스의 inline encryption 지원 확인
cat /sys/block/sda/queue/crypto/max_dun_bytes
# 8    ← DUN(Data Unit Number) 크기, 0이면 미지원

cat /sys/block/sda/queue/crypto/modes/AES-256-XTS
# 4096  ← data_unit_size, 0이면 미지원

# 2. fscrypt에서 inline encryption 사용 (Android FBE)
# /data 파티션 마운트 시 inlinecrypt 옵션 추가
mount -o inlinecrypt /dev/block/sda13 /data

# 3. 키 해제 (파일 잠금)
# fscrypt lock /data → blk_crypto_evict_key()로 HW keyslot 제거
fscrypt lock /data
# → ICE keyslot이 안전하게 소거됨
💡

dm-crypt passthrough: dm-crypt는 CONFIG_DM_DEFAULT_KEY--inline-crypt 옵션을 통해 자체 암호화 대신 blk-crypto로 요청을 전달할 수 있습니다. 이 경우 dm-crypt 계층은 키 관리만 담당하고 실제 암복호화는 ICE가 수행하여, dm-crypt의 CPU 오버헤드를 완전히 제거합니다.

blk-crypto 인라인 암호화 스택 fscrypt (FBE) dm-crypt (--inline) dm-default-key blk-crypto 프레임워크 blk_crypto_key + keyslot 관리 ICE HW 지원? Yes ICE 하드웨어 와이어 속도 암호화 No SW Fallback Crypto API (AES-NI/CE) 스토리지 디바이스 (UFS / eMMC / NVMe) ICE 경로: CPU 0% + 와이어 속도 | SW Fallback: CPU 암호화 오버헤드 발생 (dm-crypt 동등)
blk-crypto는 ICE 지원 여부에 따라 하드웨어 또는 소프트웨어 암호화 경로를 자동 선택합니다

암호화 가속기 디바이스

CPU 내장 명령어(AES-NI, ARM CE) 외에도 PCI 가속기, SoC 임베디드 엔진, 가상 디바이스 등 다양한 형태의 암호화 가속기가 커널 Crypto API에 등록됩니다. 이들은 CPU와 독립적으로 동작하므로 대량의 암호 연산을 CPU 부하 없이 처리할 수 있습니다.

AMD CCP / PSP

AMD CCP(Cryptographic Co-Processor)는 AMD EPYC/Ryzen 프로세서에 내장된 보안 전용 코프로세서입니다. PSP(Platform Security Processor)는 CCP를 포함하는 상위 개념으로, SEV/SEV-SNP 기밀 컴퓨팅(Confidential Computing)의 기반이 됩니다:

# ━━━ AMD CCP 드라이버 설정 ━━━

# Kconfig 옵션
# CONFIG_CRYPTO_DEV_CCP=m
# CONFIG_CRYPTO_DEV_CCP_CRYPTO=m  ← Crypto API 연동
# CONFIG_CRYPTO_DEV_SP_PSP=y       ← PSP/SEV 지원

# CCP 디바이스 확인
lspci | grep -i ccp
# 09:00.2 Encryption controller: AMD CCP/PSP

# 드라이버 로드 확인
dmesg | grep ccp
# ccp 0000:09:00.2: enabling device
# ccp 0000:09:00.2: ccp enabled
# ccp 0000:09:00.2: SEV API:1.55 build:21

# CCP가 등록한 Crypto API 알고리즘
grep ccp /proc/crypto | head -5
# driver       : ccp-aes-cbc
# driver       : ccp-aes-gcm
# driver       : ccp-sha256
ℹ️

CCP vs AES-NI: CCP는 전용 코프로세서로 CPU 코어와 병렬 동작하지만, 통신 오버헤드(MMIO, DMA)가 있어 소량 데이터에서는 AES-NI보다 느릴 수 있습니다. CCP의 주요 가치는 (1) SEV 메모리 암호화, (2) 대량 배치 처리, (3) CPU 부하 분산(Load Balancing)에 있습니다. CCP의 crypto priority는 AES-NI보다 낮게 설정(~200)되어, 일반 용도에서는 AES-NI가 우선 선택됩니다.

HiSilicon SEC / SEC2

HiSilicon SEC2는 Kunpeng 920(ARM 서버 SoC)에 내장된 고속 암호화 가속기입니다. hisi_sec2 드라이버를 통해 Crypto API에 등록되며, UACCE(Unified/User-space Accelerator Framework)를 통해 사용자 공간에서도 직접 접근할 수 있습니다:

특성SEC2 사양
알고리즘AES/SM4 (ECB/CBC/XTS), SHA-256/512, SM3
처리량최대 100 Gbps (AES-256-XTS)
최대 256개 하드웨어 큐
KconfigCONFIG_CRYPTO_DEV_HISI_SEC2
UACCE/dev/hisi_sec2-* 디바이스 노드

Marvell / Cavium OcteonTX CPT

CPT(Crypto Processing Thread)는 Marvell(구 Cavium) OCTEON TX2 및 CN10K 시리즈 네트워크 SoC에 내장된 암호화 가속 유닛입니다. 데이터센터·캐리어 등급 네트워크 장비를 위해 설계되었으며, IPsec ESP, TLS/DTLS, MACsec 등 프로토콜별 오프로드를 하드웨어 수준에서 지원합니다. CPT의 핵심 특징은 SoC 내부의 SSO(Schedule/Synchronize/Order) 이벤트 스케줄러와 직접 연동되어, 암호화 완료 시 CPU 개입 없이 다음 처리 단계로 패킷이 자동 전달되는 것입니다. 최대 100 Gbps 대칭키 처리량을 제공하며, cpt(OcteonTX1) 및 otx2_cpt/cnxk_cpt(OcteonTX2/CN10K) 드라이버를 통해 커널 Crypto API에 등록됩니다.

SoC별 CPT 사양

SoC 시리즈CPT 세대엔진 (AE/SE/IE)VF 지원대칭키 처리량비대칭키주요 용도
OCTEON TX (CN83xx)CPT v1SE×16최대 16 VF~25 GbpsRSA-2048 10K엔터프라이즈 라우터
OCTEON TX2 (CN96xx)CPT v2AE×4, SE×30, IE×4최대 128 VF~100 GbpsRSA-2048 50K캐리어 라우터, NGFW
OCTEON TX2 (CN98xx)CPT v2AE×8, SE×46, IE×8최대 128 VF~140 GbpsRSA-2048 80K캐리어 코어 라우터
CN10K (CN106xx)CPT v3AE×8, SE×40, IE×8최대 256 VF~100 GbpsRSA-2048 60KDPU, 5G UPF, NGFW
OCTEON 10 DPUCPT v3AE×12, SE×48, IE×12최대 256 VF~200 GbpsRSA-2048 100K클라우드 DPU, CDN
ℹ️

엔진 유형 구분: CPT v2/v3는 3종류의 마이크로엔진(Micro-Engine)으로 구성됩니다. SE(Symmetric Engine)는 AES/SHA 등 대칭키·해시 연산, AE(Asymmetric Engine)는 RSA/ECC/DH 비대칭키 연산, IE(IPsec/Inline Engine)는 ESP/AH 프로토콜 캡슐화를 전담합니다. 각 엔진은 독립적으로 동작하며, 유형별로 개수를 조정하여 워크로드에 맞게 성능을 최적화할 수 있습니다.

CPT 하드웨어 블록 구조

CPT는 SoC 내부 인터커넥트(AMBA/CCN)를 통해 CPU 코어, SSO 이벤트 스케줄러, NIX(네트워크 인터페이스) 블록과 직접 연결됩니다. PF(Physical Function)가 전체 리소스를 관리하며, VF(Virtual Function)를 통해 각 애플리케이션(커널 또는 DPDK)이 독립된 명령 큐를 사용합니다.

OCTEON CPT 하드웨어 블록 다이어그램 OCTEON TX2 / CN10K SoC ARM N2 코어 (최대 36) SSO 이벤트 스케줄러 NIX (네트워크 I/O) NPA (버퍼 풀) ZIP / REE AMBA / CCN SoC 내부 인터커넥트 CPT (Crypto Processing Thread) CPT PF 리소스 관리, 엔진 할당 VF 0 커널 Crypto API VF 1 DPDK cryptodev VF N ... 인라인 IPsec NIX 직접 연동 명령 큐(Instruction Queue) 풀 VF당 최대 64 IQ — CPT_INST_S 디스크립터 제출 → 엔진 자동 할당 SE (Symmetric Engine) AES/SHA/HMAC — 최대 48개 AE (Asymmetric Engine) RSA/ECC/DH/SM2 — 최대 12개 IE (IPsec Engine) ESP 캡슐화 전담 — 최대 12개 완료 경로: Result Queue → SSO 이벤트 자동 생성 (또는 IRQ/폴링 콜백) CPT_RES_S 상태 코드 + 출력 데이터 DMA → 다음 파이프라인 스테이지로 자동 전달
CPT는 PF/VF 모델로 리소스를 격리하며, 3종 마이크로엔진(SE/AE/IE)이 명령 큐에서 디스크립터를 가져와 독립 실행합니다

CPT 명령 제출/완료 흐름

CPT의 모든 암호 연산은 CPT_INST_S(Instruction Structure) 디스크립터를 명령 큐(IQ)에 기록하는 방식으로 제출됩니다. 각 VF는 독립된 IQ를 보유하며, CPT 하드웨어가 빈 엔진을 자동으로 할당합니다. 완료 결과는 CPT_RES_S(Result Structure)로 전달되며, SSO 이벤트 또는 인터럽트/폴링 방식으로 통지됩니다.

CPT 명령 제출/완료 흐름 (Lookaside 모드) ① CPT_INST_S 빌드 opcode, 입출력 scatter/ gather 포인터, ctx 주소 ② IQ에 LMTST 쓰기 LMTST: 단일 캐시라인 원자적(atomic) 제출 ③ 엔진 할당·실행 SE/AE/IE 자동 선택 DMA로 데이터 처리 ④ 완료 CPT_RES_S 상태+결과 경로 A: SSO 이벤트 생성 CPU 무개입, 다음 스테이지 자동 전달 경로 B: IRQ / 폴링 커널 Crypto API 콜백 CPT_INST_S 구조 (128바이트) Word 0-1: 입력 scatter/gather 리스트 (dptr) Word 2-3: 출력 scatter/gather 리스트 (rptr) Word 4: CPT_INST_W4 — opcode, 입출력 길이 Word 5: ctx 포인터 (세션 키·IV·상태) Word 6: 태그(SSO) / 그룹 / 완료 통지 모드 Word 7: 결과 주소 (CPT_RES_S 기록 위치) LMTST(Load/Store Multiple Thread): ARM 캐시라인 단위 원자적 쓰기 — PCIe MMIO 대비 ~5배 빠른 제출 지연 SSO 경로(A)는 DPAA/이벤트 기반 파이프라인, IRQ 경로(B)는 커널 Crypto API 범용 사용
CPT_INST_S 디스크립터를 LMTST로 원자적 제출 → 엔진이 DMA 처리 → 결과를 SSO 이벤트 또는 IRQ로 전달합니다

CPT ↔ SSO 연동 (이벤트 드리븐 파이프라인)

OCTEON의 가장 강력한 특성은 CPT와 SSO(Schedule/Synchronize/Order) 이벤트 스케줄러의 하드웨어 직접 연동입니다. NGFW/IPsec 파이프라인에서 패킷이 암호화 처리를 거칠 때, CPU가 CPT 완료를 폴링하거나 인터럽트를 기다릴 필요 없이 CPT가 완료 이벤트를 SSO에 직접 제출합니다:

CPT ↔ SSO 이벤트 드리븐 IPsec 파이프라인 NIX RX 패킷 수신 WQE 생성 SSO Atomic 스케줄 → FW 코어 할당 방화벽 ACL / conntrack IPsec 필요 판단 CPT ESP 암호화/복호화 CPU 무개입 완료 → SSO 자동 CPT 완료 → SSO 이벤트 자동 생성 (CPU 무개입) 후처리 IPS / QoS NIX TX 패킷 송신 일반 SoC (잡 링 / IRQ 방식) CPU → 제출 → 대기(IRQ/폴링) → 완료 확인 → 다음 단계 CPU가 매 패킷마다 암호화 완료를 처리해야 함 OCTEON (CPT ↔ SSO 연동) CPU → 제출(SSO FWD) → [CPT 처리] → SSO가 자동 전달 CPU는 암호화 중 다른 패킷 처리 가능 — 파이프라인 효율 극대화
CPT 완료 시 SSO가 CPU 개입 없이 이벤트를 다음 스테이지로 전달하여, 기존 IRQ/폴링 방식 대비 파이프라인 효율이 크게 향상됩니다

Linux 드라이버 소스 구조

CPT 드라이버는 drivers/crypto/marvell/octeontx2/에 위치하며, PF/VF 분리 구조입니다:

drivers/crypto/marvell/
├── octeontx/                    /* OcteonTX1 CPT v1 드라이버 */
│   ├── otx_cptvf_main.c         /* VF 드라이버 (crypto API 등록) */
│   ├── otx_cptvf_algs.c         /* skcipher·aead 알고리즘 */
│   └── otx_cptvf_reqmgr.c      /* 명령 큐 관리 */
├── octeontx2/                   /* OcteonTX2/CN10K CPT v2/v3 드라이버 */
│   ├── otx2_cptpf_main.c       /* PF: 엔진 그룹 할당, VF 관리 */
│   ├── otx2_cptvf_main.c       /* VF: Crypto API 등록 */
│   ├── otx2_cptvf_algs.c       /* skcipher·aead·ahash 알고리즘 */
│   ├── otx2_cpt_reqmgr.c       /* IQ 제출, 완료 폴링/IRQ */
│   ├── otx2_cptlf.c            /* LF(Logical Function) 관리 */
│   ├── cn10k_cpt.c             /* CN10K 전용 로직 (CPT v3) */
│   └── otx2_cpt_common.h       /* CPT_INST_S, CPT_RES_S 정의 */
└── Kconfig / Makefile

Kconfig 설정과 드라이버 확인

# ━━━ Marvell CPT 드라이버 설정 ━━━

# Kconfig 옵션
# CONFIG_CRYPTO_DEV_OCTEONTX_CPT=m         ← OcteonTX1 CPT v1
# CONFIG_CRYPTO_DEV_OCTEONTX2_CPT=m        ← OcteonTX2/CN10K CPT v2/v3

# PF/VF 디바이스 확인
lspci | grep -i cpt
# 0002:0e:00.0 Encryption controller: Cavium CPT PF (rev 54)
# 0002:0e:00.1 Encryption controller: Cavium CPT VF (rev 54)

# 드라이버 로드 확인
dmesg | grep -i cpt
# otx2_cpt 0002:0e:00.0: CPT PF: eng grps=8, SE=30, AE=4, IE=4
# otx2_cpt 0002:0e:00.1: CPT VF[0] ready

# 엔진 그룹 할당 상태 (sysfs)
cat /sys/bus/pci/drivers/otx2_cpt/*/eng_grp_info
# Engine Group 0: SE [cipher/hash]  engines: 30
# Engine Group 1: AE [asymmetric]   engines: 4
# Engine Group 2: IE [ipsec inline] engines: 4

# CPT가 등록한 Crypto API 알고리즘
grep -E "^driver|^name|^priority" /proc/crypto | grep -B2 otx2
# name         : cbc(aes)
# driver       : otx2-cpt-cbc-aes
# priority     : 4001
# ---
# name         : gcm(aes)
# driver       : otx2-cpt-gcm-aes
# priority     : 4001
# ---
# name         : sha256
# driver       : otx2-cpt-sha256
# priority     : 4001
# ---
# name         : rsa
# driver       : otx2-cpt-rsa
# priority     : 4001
# ---
# name         : authenc(hmac(sha256),cbc(aes))
# driver       : otx2-cpt-authenc-hmac-sha256-cbc-aes
# priority     : 4001

인라인 IPsec 모드

CPT v2/v3의 IE(IPsec Engine)는 인라인(Inline) IPsec 모드를 지원합니다. 이 모드에서는 NIX(네트워크 인터페이스)가 수신·송신 패킷을 CPT에 직접 전달하여 ESP 캡슐화/역캡슐화를 수행하며, CPU는 SA(Security Association) 설정만 담당합니다. xfrm 프레임워크의 offload 모드로 활성화됩니다:

# ━━━ OCTEON 인라인 IPsec 설정 ━━━

# xfrm offload 모드로 SA 추가
ip xfrm state add \
    src 192.168.1.1 dst 10.0.0.1 \
    proto esp spi 0x100 reqid 1 mode tunnel \
    aead "rfc4106(gcm(aes))" 0x$(xxd -l 20 -p /dev/urandom) 128 \
    offload dev eth0 dir out  # ← offload 키워드가 핵심

# 인라인 IPsec 통계 확인
ethtool -S eth0 | grep ipsec
# ipsec_tx_pkts: 1234567
# ipsec_tx_bytes: 890123456
# ipsec_rx_pkts: 1234560
# ipsec_rx_bytes: 890120000

# SA 오프로드 상태 확인
ip xfrm state show | grep offload
# offload dev eth0 dir out
# → NIX TX → CPT IE → 와이어 (CPU 0% 암호화 부하)

성능 특성

알고리즘OcteonTX1 (25G)OcteonTX2 CN96 (100G)CN10K (100G)OCTEON 10 DPU (200G)
AES-128-CBC~20 Gbps~95 Gbps~100 Gbps~180 Gbps
AES-256-GCM~15 Gbps~80 Gbps~100 Gbps~170 Gbps
SHA-256~18 Gbps~70 Gbps~80 Gbps~150 Gbps
RSA-2048 Sign~10K op/s~50K op/s~60K op/s~100K op/s
RSA-2048 Verify~60K op/s~200K op/s~250K op/s~400K op/s
ECDSA P-256 Sign~15K op/s~80K op/s~100K op/s~150K op/s
인라인 IPsec (GCM)미지원~100 Gbps~100 Gbps~200 Gbps
💡

우선순위와 자동 선택: CPT 드라이버의 crypto priority는 4001로, 대부분의 소프트웨어 구현(100~400)과 다른 SoC 가속기(CAAM 3000, CCP 200)보다 높습니다. OCTEON 플랫폼에서는 별도 설정 없이 CPT가 기본 암호 제공자(crypto provider)로 자동 선택됩니다.

SR-IOV 격리: CPT PF는 최대 128~256개 VF를 생성할 수 있어, 각 VM/컨테이너에 독립된 암호 가속 인스턴스를 할당할 수 있습니다. VF 간 큐와 엔진이 완전히 격리되므로 멀티테넌트 환경에서 안전합니다.

DPDK 연동: DPDK crypto_cnxk(CN10K) / crypto_octeontx2(TX2) PMD를 통해 유저스페이스에서 CPT에 직접 접근할 수 있습니다. SSO eventdev(event_cnxk)와 결합하면 CPU 개입 없는 완전 HW 파이프라인이 구성됩니다. 상세 파이프라인 코드는 NGFW 오프로드 — OCTEON SSO를 참고하세요.

Lookaside vs Inline: Lookaside 모드는 범용 암호화(dm-crypt, AF_ALG)에, Inline 모드는 IPsec/MACsec 네트워크 오프로드에 사용합니다. 두 모드를 동시에 사용할 수 있으며, IE 엔진 수를 조정하여 워크로드에 맞게 리소스를 분배합니다.

NXP CAAM (Cryptographic Acceleration and Assurance Module)

CAAM(Cryptographic Acceleration and Assurance Module)은 NXP i.MX6/7/8 시리즈 및 Layerscape LS1/LS2 시리즈 SoC에 내장된 암호화 가속 모듈입니다. 잡 링(Job Ring) 기반 비동기 아키텍처가 핵심으로, 최대 4개의 독립 잡 링(JR0~JR3)이 각각 자체 인터럽트와 디스크립터 큐를 갖고 있어 멀티코어 환경에서 lock-free 병렬 처리가 가능합니다. 내부적으로 DECO(DEscriptor COntroller) 엔진이 디스크립터를 독립 실행하며, AES(전 모드), SHA-1/256/384/512, RSA(최대 4096비트), ECDSA, 하드웨어 난수 생성기(TRNG)를 지원합니다. Layerscape SoC에서는 잡 링 외에 QI(Queue Interface)를 통해 DPAA(Data Path Acceleration Architecture) 프레임 큐와 직접 연동할 수 있어, 네트워크 데이터 플레인에서 CPU 개입 없는 인라인 암호화가 가능합니다.

SoC별 CAAM 사양

SoC 시리즈CAAM 버전잡 링DECOQI대칭키 처리량주요 용도
i.MX6 시리즈SEC 4.x2 JR2~3없음~300 MbpsIoT 게이트웨이, 산업용
i.MX7 시리즈SEC 4.x2 JR2없음~300 Mbps웨어러블, 저전력 IoT
i.MX8M 시리즈SEC 4.x3 JR3없음~1 Gbps멀티미디어, 엣지 AI
i.MX8X / i.MX8QXPSEC 5.x4 JR4없음~1.5 Gbps자동차(ADAS), 산업용
i.MX9 시리즈SEC 5.x (EdgeLock)4 JR4+없음~2 Gbps자동차, 산업 IoT
LS1012ASEC 5.x2 JR4있음~2.5 Gbps소형 라우터, CPE
LS1028ASEC 5.x4 JR6있음~5 GbpsTSN 스위치, 산업용
LS1043A/LS1046ASEC 5.x4 JR6~8있음~10 Gbps엔터프라이즈 라우터
LS1088ASEC 5.x4 JR8있음~10 Gbps네트워크 어플라이언스
LS2088A/LX2160ASEC 5.44 JR12~16있음~20 Gbps캐리어 등급 라우터

CAAM 하드웨어 블록 구조

CAAM은 내부적으로 잡 링(JR), QI(Queue Interface), DECO, 암호 코어의 4개 주요 블록으로 구성됩니다. 잡 링은 CPU가 디스크립터를 제출하는 일반적인 인터페이스이며, QI는 DPAA 프레임워크의 프레임 큐(Frame Queue)와 직접 연동하여 네트워크 패킷을 CPU 개입 없이 처리합니다.

CAAM 하드웨어 블록 다이어그램 NXP SoC (i.MX / Layerscape) CPU Core 0 CPU Core 1 CPU Core N DPAA / 네트워크 컨트롤러 AXI / SoC 내부 인터커넥트 CAAM (SEC 엔진) 잡 링 0~3 Input/Output Ring QI DPAA Frame Queue 컨트롤러 스케줄러·DMA RNG (TRNG) /dev/hwrng DECO(DEscriptor COntroller) 엔진 풀 DECO 0 | DECO 1 | DECO 2 | DECO 3 | ... | DECO N (SoC별 2~16개) AES 엔진 ECB/CBC/CTR/GCM/CCM SHA 엔진 SHA-1/256/512 PKHA RSA/ECC/DH DES/3DES 레거시 CRC 무결성 보안 메모리(Secure Memory) — 블랙 키·마스터 키·Blob 데이터 보호 영역 (외부 접근 차단)
CAAM 내부 블록 구조: 잡 링(CPU 인터페이스)과 QI(DPAA 인터페이스) 두 경로를 통해 DECO 엔진이 암호 연산을 수행합니다

잡 디스크립터와 링 메커니즘

CAAM의 모든 암호 연산은 잡 디스크립터(Job Descriptor)라는 명령어 시퀀스로 기술됩니다. 각 잡 링은 입력 링(Input Ring)출력 링(Output Ring)으로 구성된 한 쌍의 순환 버퍼(circular buffer)입니다. CPU가 디스크립터 포인터를 입력 링에 기록하고 도어벨(doorbell) 레지스터를 쓰면, CAAM은 빈 DECO에 디스크립터를 할당하여 실행합니다. 완료 후 결과 포인터가 출력 링에 기록되고 인터럽트가 발생합니다.

잡 디스크립터 제출/완료 흐름 ① CPU가 디스크립터 작성 CPU (caam_jr) 디스크립터 DMA 주소 입력 링 (Input Ring) 순환 버퍼, 최대 1024 슬롯 ③ 도어벨 Write ④ DECO 엔진 실행 디스크립터 fetch → DMA 입력 → 암호 연산 → DMA 출력 ⑤ 출력 링 (Output Ring) 완료 디스크립터 + 상태 코드 ⑥ IRQ → 콜백 crypto_request 완료 알림 잡 디스크립터 구조 ┌─ Header (알고리즘, 길이) ├─ Key 로드 명령 ├─ FIFO Load (입력 데이터 DMA) ├─ Operation (AES-CBC-ENC 등) └─ FIFO Store (출력 데이터 DMA) 전체 과정이 DMA 기반 — CPU는 디스크립터 제출·완료 콜백만 처리 (데이터 복사 없음)
CPU가 디스크립터를 입력 링에 기록하고 도어벨을 울리면, CAAM이 자체적으로 DMA를 통해 데이터를 처리하고 완료를 알립니다

QI(Queue Interface)와 DPAA 연동

Layerscape SoC에서 CAAM은 잡 링 외에 QI(Queue Interface) 경로를 제공합니다. QI는 DPAA(Data Path Acceleration Architecture)의 QMan(Queue Manager) 프레임 큐와 직접 연동되어, 네트워크 패킷이 CPU를 거치지 않고 CAAM으로 전달됩니다. IPsec ESP 처리에서 이 경로를 사용하면 CPU 오버헤드가 크게 줄어듭니다:

잡 링 경로 vs QI(DPAA) 경로 비교 잡 링 경로 (범용) NIC RX CPU (xfrm) 잡 링 CAAM CPU (콜백) TX QI 경로 (DPAA 네트워크) DPAA FMan QMan FQ CAAM QI QMan FQ TX CPU 개입 없음 — QMan이 CAAM에 직접 프레임 전달 잡 링 경로 QI 경로 CPU가 디스크립터 제출·콜백 처리 QMan FQ ↔ CAAM 직접 DMA 범용 (dm-crypt, AF_ALG 등) DPAA IPsec 특화 (Layerscape만)
잡 링 경로는 CPU가 관여하는 범용 인터페이스, QI 경로는 DPAA 프레임워크와 직접 연동하여 CPU 오버헤드를 제거합니다
# ━━━ QI 인터페이스 활성화 (Layerscape) ━━━

# Kconfig: CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API_QI=m
# QI 드라이버가 DPAA QMan 프레임 큐를 통해 CAAM에 직접 접근

# QI 활성 확인
dmesg | grep qi_init
# caam_qi: registering rng-caam-qi
# caam_qi: 2 congestion groups, threshold 512

# QI 기반 알고리즘은 별도 드라이버명으로 등록
grep caam-qi /proc/crypto
# driver       : authenc-hmac-sha256-cbc-aes-caam-qi
# driver       : gcm-aes-caam-qi

Linux 드라이버 소스 구조

CAAM 드라이버는 drivers/crypto/caam/에 위치하며, 기능별로 모듈화되어 있습니다:

drivers/crypto/caam/
├── ctrl.c              /* CAAM 컨트롤러 프로브·초기화 (platform_driver) */
├── jr.c                /* 잡 링 드라이버 (입출력 링 관리, IRQ 핸들러) */
├── caamalg.c           /* skcipher·aead 알고리즘 (JR 경로) */
├── caamalg_qi.c        /* skcipher·aead 알고리즘 (QI 경로, Layerscape) */
├── caamalg_qi2.c       /* skcipher·aead 알고리즘 (DPAA2 QI, LX2160A 등) */
├── caamhash.c          /* ahash 알고리즘 (SHA, HMAC) */
├── caampkc.c           /* RSA·ECC 비대칭키 알고리즘 */
├── caamrng.c           /* 하드웨어 RNG (/dev/hwrng) */
├── key_gen.c           /* 키 확장·분할 키(Split Key) 생성 */
├── desc_constr.h       /* 디스크립터 빌더 매크로 (inline_cnstr_*) */
├── desc.h              /* SEC 명령어 정의 (OP_TYPE, OP_ALG 등) */
├── regs.h              /* CAAM 레지스터 맵 (JR 레지스터, 도어벨 등) */
├── intern.h            /* 내부 자료구조 (caam_drv_private 등) */
├── blob_gen.c          /* Blob 프로토콜 (블랙 키 캡슐화) */
└── sm.h                /* 보안 메모리(Secure Memory) 인터페이스 */

Kconfig 설정과 드라이버 확인

# ━━━ NXP CAAM 드라이버 설정 ━━━

# Kconfig 옵션
# CONFIG_CRYPTO_DEV_FSL_CAAM=y               ← CAAM 컨트롤러 (ctrl.c)
# CONFIG_CRYPTO_DEV_FSL_CAAM_JR=m            ← Job Ring 드라이버 (jr.c)
# CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API=m    ← 대칭키·AEAD (caamalg.c)
# CONFIG_CRYPTO_DEV_FSL_CAAM_CRYPTO_API_QI=m ← QI 경로 (caamalg_qi.c)
# CONFIG_CRYPTO_DEV_FSL_CAAM_AHASH_API=m     ← 해시 (caamhash.c)
# CONFIG_CRYPTO_DEV_FSL_CAAM_PKC_API=m       ← RSA/ECC (caampkc.c)
# CONFIG_CRYPTO_DEV_FSL_CAAM_RNG_API=m       ← /dev/hwrng (caamrng.c)
# CONFIG_CRYPTO_DEV_FSL_CAAM_BLOB_GEN=y      ← Blob 캡슐화 (blob_gen.c)

# Job Ring 디바이스 확인
dmesg | grep caam
# caam 1700000.crypto: device ID = 0x0A16010000000000
# caam 1700000.crypto: job rings = 4, qi = 1
# caam 1700000.crypto: desc_fifo_len = 63
# caam_jr 1710000.jr: registering rng-caam
# caam_jr 1720000.jr: registering cbc-aes-caam

# CAAM이 등록한 Crypto API 알고리즘 전체 목록
grep -E "^driver|^name|^priority" /proc/crypto | grep -B2 caam
# name         : cbc(aes)
# driver       : cbc-aes-caam
# priority     : 3000
# ---
# name         : gcm(aes)
# driver       : gcm-aes-caam
# priority     : 3000
# ---
# name         : sha256
# driver       : sha256-caam
# priority     : 3000
# ---
# name         : rsa
# driver       : rsa-caam
# priority     : 3000
# ---
# name         : authenc(hmac(sha256),cbc(aes))
# driver       : echainiv-authenc-hmac-sha256-cbc-aes-caam
# priority     : 3100

# CAAM hwrng 상태
cat /sys/class/misc/hw_random/rng_current
# rng-caam

Device Tree 바인딩

CAAM은 SoC에 메모리 맵(MMIO) 방식으로 연결되며, Device Tree에서 잡 링·RNG·보안 메모리 등을 개별 자식 노드로 선언합니다:

/* NXP Layerscape LS1046A — CAAM Device Tree 바인딩 */
crypto@1700000 {
    compatible = "fsl,sec-v5.4", "fsl,sec-v5.0",
                 "fsl,sec-v4.0";
    /* CAAM 레지스터 영역 (컨트롤러 + 보안 메모리) */
    reg = <0x0 0x1700000 0x0 0x100000>;
    ranges = <0x0 0x0 0x1700000 0x100000>;
    #address-cells = <1>;
    #size-cells = <1>;
    fsl,sec-era = <8>;  /* SEC 하드웨어 세대 */

    /* Job Ring 0 — 독립 IRQ, 커널 암호 API용 */
    jr@10000 {
        compatible = "fsl,sec-v5.4-job-ring",
                     "fsl,sec-v5.0-job-ring",
                     "fsl,sec-v4.0-job-ring";
        reg = <0x10000 0x10000>;
        interrupts = <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>;
    };

    /* Job Ring 1 — 독립 IRQ */
    jr@20000 {
        compatible = "fsl,sec-v5.4-job-ring",
                     "fsl,sec-v5.0-job-ring",
                     "fsl,sec-v4.0-job-ring";
        reg = <0x20000 0x10000>;
        interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
    };

    /* Job Ring 2 — OP-TEE/TF-A에 할당 가능 */
    jr@30000 {
        compatible = "fsl,sec-v5.4-job-ring",
                     "fsl,sec-v5.0-job-ring",
                     "fsl,sec-v4.0-job-ring";
        reg = <0x30000 0x10000>;
        interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
    };

    /* Job Ring 3 */
    jr@40000 {
        compatible = "fsl,sec-v5.4-job-ring",
                     "fsl,sec-v5.0-job-ring",
                     "fsl,sec-v4.0-job-ring";
        reg = <0x40000 0x10000>;
        interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
    };
};
💡

OP-TEE와 잡 링 분할: ARM TrustZone 환경에서 OP-TEE가 하나의 잡 링(보통 JR2)을 독점 사용하는 경우, 해당 잡 링의 Device Tree 노드에 status = "disabled"를 설정하여 Linux 커널이 접근하지 않도록 합니다. i.MX8M에서 OP-TEE + CAAM 조합이 일반적이며, 이 경우 Linux는 나머지 2~3개 잡 링만 사용합니다.

CAAM Blob 메커니즘 (블랙 키)

CAAM의 고유한 보안 기능 중 하나가 Blob 프로토콜입니다. Blob은 CAAM의 마스터 키(OTP에 퓨즈된 SoC 고유 키)로 데이터를 캡슐화(encapsulation)하는 메커니즘으로, 해당 SoC에서만 복호화할 수 있습니다. 이를 통해 키 자료를 외부 저장소(eMMC, NAND)에 안전하게 보관할 수 있습니다:

/* CAAM Blob 캡슐화/역캡슐화 — drivers/crypto/caam/blob_gen.c */

/* Blob 구조: [Blob Header(16B)] + [암호화된 키 데이터] + [MAC(16B)] */
#define CAAM_BLOB_OVERHEAD   (32 + 16)  /* 헤더 + MAC */

/* 캡슐화: 레드 키 → Blob */
struct caam_blob_priv *blob = caam_blob_gen_init();
struct caam_blob_info info = {
    .input  = red_key,              /* 평문 키 */
    .input_len = key_len,
    .output = blob_buf,             /* Blob 출력 버퍼 */
    .output_len = key_len + CAAM_BLOB_OVERHEAD,
    .key_mod = key_modifier,        /* 키 한정자 (용도별 분리) */
    .key_mod_len = 16,
};
caam_encap_blob(blob, &info);     /* HW 마스터 키로 캡슐화 */

/* 역캡슐화: Blob → 레드 키 (동일 SoC에서만 성공) */
caam_decap_blob(blob, &info);     /* HW 마스터 키로 복호화 */

/* Trusted Keys에서 CAAM Blob 활용 (Linux 5.13+) */
/* keyctl add trusted mykey "new 32 keyhandle=caam" @u */
/* → CAAM Blob으로 키 실링, 파일시스템에 안전하게 저장 */

디스크립터 빌드 예제

CAAM 드라이버는 desc_constr.h의 매크로를 사용하여 하드웨어 디스크립터를 빌드합니다. 아래는 AES-CBC 암호화 디스크립터의 구성 과정입니다:

/* caamalg.c — AES-CBC 암호화 디스크립터 빌드 (간소화) */
#include "desc_constr.h"

u32 *desc = ctx->sh_desc_enc;

/* 공유 디스크립터(Shared Descriptor) 헤더 */
init_sh_desc(desc, HDR_SHARE_SERIAL);

/* 1. 키 로드 — DMA로 키를 CAAM 키 레지스터에 적재 */
append_key_aead(desc, ctx->key_dma, ctx->adata.keylen_pad,
                ctx->cdata.keylen, ctx->cdata.key_virt);

/* 2. 잡 디스크립터 헤더 — 잡별 가변 파라미터 시작점 */
set_jump_tgt_here(desc, key_jump_cmd);

/* 3. IV 로드 — CBC 초기 벡터를 CONTEXT1에 적재 */
append_seq_in_ptr_intlen(desc, 0, ivsize, LDST_SGF);
append_cmd(desc, CMD_SEQ_LOAD | LDST_SRCDST_BYTE_CONTEXT |
           LDST_CLASS_1_CCB | ivsize);

/* 4. 암호화 연산 — AES-CBC 인코드 */
append_operation(desc, ctx->cdata.algtype |
                 OP_ALG_AS_INITFINAL | OP_ALG_ENCRYPT);

/* 5. FIFO Load/Store — DMA로 입력 데이터 읽기·출력 데이터 쓰기 */
append_seq_fifo_load(desc, 0, FIFOLD_CLASS_CLASS1 |
                     FIFOLD_TYPE_MSG | FIFOLD_TYPE_LAST1);
append_seq_fifo_store(desc, 0, FIFOST_TYPE_MESSAGE_DATA);

/* 디스크립터 완성 — DECO가 이 시퀀스를 순차 실행 */
ℹ️

공유 디스크립터 vs 잡 디스크립터: CAAM은 두 종류의 디스크립터를 사용합니다. 공유 디스크립터(Shared Descriptor)는 알고리즘·키 등 세션 단위로 변하지 않는 명령을 담고, 잡 디스크립터(Job Descriptor)는 IV·데이터 포인터 등 요청마다 달라지는 파라미터를 담습니다. DECO는 두 디스크립터를 결합하여 하나의 연산으로 실행합니다.

성능 특성과 활용 가이드

알고리즘i.MX8M (1 Gbps)LS1046A (10 Gbps)LS2088A (20 Gbps)비고
AES-128-CBC~800 Mbps~8 Gbps~18 Gbps블록 암호 기본
AES-256-GCM~600 Mbps~7 Gbps~15 GbpsIPsec ESP 표준
SHA-256~700 Mbps~6 Gbps~14 GbpsHMAC 포함
RSA-2048 Sign~500 op/s~2,000 op/s~5,000 op/s비대칭 연산
RSA-2048 Verify~5,000 op/s~20,000 op/s~50,000 op/s검증이 훨씬 빠름
ECDSA P-256 Sign~1,000 op/s~4,000 op/s~10,000 op/sTLS 핸드셰이크
RNG (TRNG)~100 Mbps~100 Mbps~100 Mbps/dev/hwrng
💡

우선순위와 자동 선택: CAAM 드라이버의 crypto priority는 3000~4000으로 소프트웨어 fallback(100~300)보다 높습니다. 따라서 i.MX/Layerscape 플랫폼에서는 별도 설정 없이 CAAM이 기본 암호 제공자(crypto provider)로 자동 선택됩니다. AEAD 복합 알고리즘(authenc(hmac(sha256),cbc(aes)))은 priority 3100으로 더 높게 설정됩니다.

Trusted Keys 연동: Linux 5.13부터 CONFIG_TRUSTED_KEYS_CAAM을 통해 TPM 없이도 CAAM의 Blob 메커니즘으로 키 실링(sealing)이 가능합니다. 상세는 커널 키링 — trusted 키 타입을 참고하세요.

네트워크 오프로드: Layerscape 플랫폼에서는 CAAM QI 경로를 통한 IPsec xfrm 오프로드가 가능합니다. Device Tree 바인딩 상세와 DPAA 연동 설정은 NGFW 하드웨어 오프로드 페이지를 참고하세요.

성능 최적화 팁: (1) 잡 링을 CPU 코어에 1:1 매핑(irq_set_affinity)하면 lock 경합이 제거됩니다. (2) 소형 패킷이 많은 경우 디스크립터 배치(batching)로 도어벨 쓰기를 줄이세요. (3) Layerscape에서 IPsec 트래픽은 JR보다 QI 경로가 CPU 효율이 훨씬 높습니다.

Samsung Exynos S5P SSS

Samsung S5P SSS(Security SubSystem)는 Exynos SoC에 내장된 암호 엔진입니다. s5p-sss 드라이버가 AES(ECB/CBC/CTR), SHA-1/256, PRNG를 Crypto API에 등록합니다. 주로 Android 기기에서 dm-crypt 가속에 활용됩니다.

SoC 임베디드 암호 엔진 종합

벤더SoC / 칩드라이버Kconfig지원 알고리즘
BroadcomBCM58xx SPUbcm-spuCRYPTO_DEV_BCM_SPUAES, SHA, 3DES, MD5
Allwinnersun8i/sun50i CEsun8i-ceCRYPTO_DEV_SUN8I_CEAES, SHA, PRNG
RockchipRK3399 cryptork3288-cryptoCRYPTO_DEV_ROCKCHIPAES, SHA, MD5
AmlogicGXL/G12amlogic-gxlCRYPTO_DEV_AMLOGIC_GXLAES (CBC/ECB)
STMicroSTM32 CRYPstm32-crypCRYPTO_DEV_STM32_CRYPAES, DES, 3DES
Texas Instr.OMAP/AM65x SA2ULsa2ulCRYPTO_DEV_SA2ULAES, SHA, HMAC
NXPi.MX CAAMcaamCRYPTO_DEV_FSL_CAAMAES, SHA, RSA, RNG
MicrochipATMEL AES/SHAatmel-aesCRYPTO_DEV_ATMEL_AESAES, SHA

virtio-crypto 가상 디바이스

virtio-crypto는 가상 머신에서 호스트의 암호화 가속기를 활용할 수 있는 준가상화(paravirtual) 디바이스입니다. QEMU/KVM 환경에서 게스트 커널이 virtio_crypto 드라이버를 통해 호스트의 Crypto API(또는 하드웨어 가속기)에 접근합니다:

# ━━━ QEMU에서 virtio-crypto 디바이스 추가 ━━━

# 방법 1: 내장 백엔드
qemu-system-x86_64 \
    -device virtio-crypto-pci,cryptodev=crypto0 \
    -object cryptodev-backend-builtin,id=crypto0

# 방법 2: vhost-user 백엔드 (DPDK cryptodev 연동)
qemu-system-x86_64 \
    -chardev socket,id=chardev0,path=/tmp/vhost-crypto.sock \
    -object cryptodev-vhost-user,id=crypto0,chardev=chardev0 \
    -device virtio-crypto-pci,cryptodev=crypto0
/* virtio_crypto 서비스 유형 */
#define VIRTIO_CRYPTO_SERVICE_CIPHER  0  /* 대칭 암호 */
#define VIRTIO_CRYPTO_SERVICE_HASH    1  /* 해시 */
#define VIRTIO_CRYPTO_SERVICE_MAC     2  /* MAC */
#define VIRTIO_CRYPTO_SERVICE_AEAD    3  /* AEAD */
#define VIRTIO_CRYPTO_SERVICE_AKCIPHER 4  /* 비대칭 암호 (RSA) */

/* 게스트 커널의 /proc/crypto에서 확인 */
/* driver: virtio-crypto-xxx */
💡

DPDK cryptodev 연동: DPDK 기반 가상 스위치(OVS-DPDK)에서 암호화 처리가 필요한 경우 vhost-user-crypto 백엔드를 통해 호스트의 QAT 가속기를 게스트에 노출할 수 있습니다. DPDK cryptodev의 상세는 DPDK 암호화 가속을 참고하세요.

암호화 가속기 분류 체계 Crypto API 통합 인터페이스 CPU ISA 명령어 AES-NI, SHA-NI, ARM CE 최저 지연, 소량 데이터 최적 PCI 가속기 Intel QAT, AMD CCP 고처리량, 배치 처리 최적 SoC 임베디드 엔진 HiSilicon SEC2, NXP CAAM ARM 서버/임베디드 특화 가상 디바이스 virtio-crypto, vhost-user VM 환경 호스트 가속기 활용 모든 가속기는 동일한 Crypto API 인터페이스로 등록 → 사용자(dm-crypt, IPsec 등)는 구현을 몰라도 됨
4가지 유형의 가속기가 모두 Crypto API에 등록되어 동일한 인터페이스로 활용됩니다

AF_ALG 사용자 공간 인터페이스

AF_ALG은 사용자 공간에서 커널 Crypto API를 직접 사용할 수 있는 소켓(Socket) 인터페이스입니다. 별도의 암호 라이브러리 없이 커널에 등록된 모든 암호화 가속기(AES-NI, QAT 등)를 활용할 수 있으며, splice()를 통한 zero-copy 전송도 지원합니다.

AF_ALG 아키텍처

AF_ALG 소켓은 4단계 라이프사이클을 따릅니다:

/* AF_ALG 기본 패턴 */
int sockfd, connfd;
struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type   = "skcipher",  /* "hash", "aead", "rng" */
    .salg_name   = "cbc(aes)",  /* 알고리즘 이름 */
};

/* 1. 소켓 생성 + 알고리즘 바인드 */
sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));

/* 2. 키 설정 (skcipher/aead만) */
setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, key, key_len);

/* 3. 연결 소켓 생성 (세션) */
connfd = accept(sockfd, NULL, 0);

/* 4. 데이터 송수신 (IV/AAD는 cmsg로 전달) */
sendmsg(connfd, &msg, 0);   /* 평문 + cmsg(IV, op) */
read(connfd, out, out_len); /* 암호문 수신 */

close(connfd);
close(sockfd);
코드 설명

사용자 공간에서 커널 Crypto API에 접근하는 AF_ALG 소켓 인터페이스의 기본 4단계 패턴입니다 (crypto/af_alg.c, crypto/algif_skcipher.c 등).

  • socket(AF_ALG, SOCK_SEQPACKET, 0)AF_ALG 패밀리의 순서 보장 소켓을 생성합니다. SOCK_SEQPACKET은 메시지 경계를 보존하므로 암호 연산의 입출력 경계가 명확합니다.
  • bind() + sockaddr_algsalg_type("skcipher", "hash", "aead", "rng")과 salg_name(알고리즘 정규 이름)을 지정하여 커널이 최적 우선순위의 알고리즘을 바인드합니다.
  • setsockopt(ALG_SET_KEY)대칭 암호 및 AEAD 알고리즘에서 키를 설정합니다. 이 호출은 커널 내부에서 crypto_skcipher_setkey()를 트리거합니다.
  • accept()연산 세션(operation socket)을 생성합니다. 각 accept()마다 독립된 암호 상태가 할당되므로, 멀티스레드 환경에서 별도 세션을 사용하면 안전합니다.
  • sendmsg() + cmsgIV, 연산 방향(ALG_OP_ENCRYPT/ALG_OP_DECRYPT), AAD 길이 등을 제어 메시지(cmsg)로 전달하고, 데이터는 iovec으로 전송합니다.

해시 예제 (SHA-256)

#include <linux/if_alg.h>
#include <sys/socket.h>

int sha256_af_alg(const void *data, size_t len,
                  unsigned char digest[32])
{
    struct sockaddr_alg sa = {
        .salg_family = AF_ALG,
        .salg_type   = "hash",
        .salg_name   = "sha256",
    };
    int sockfd, connfd;

    sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
    bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
    connfd = accept(sockfd, NULL, 0);

    /* 데이터 전송 — 커널이 sha256-ni(또는 최적 드라이버)로 처리 */
    write(connfd, data, len);

    /* 다이제스트 수신 */
    read(connfd, digest, 32);

    close(connfd);
    close(sockfd);
    return 0;
}

/* splice() zero-copy: 파일 해시를 커널 공간에서 직접 처리 */
int sha256_file_splice(int fd, unsigned char digest[32])
{
    /* ... socket/bind/accept 동일 ... */
    int pipefd[2];
    pipe(pipefd);

    /* 파일 → 파이프 → AF_ALG: 사용자 공간 복사 없음 */
    splice(fd, NULL, pipefd[1], NULL, file_size, 0);
    splice(pipefd[0], NULL, connfd, NULL, file_size, 0);

    read(connfd, digest, 32);
    /* ... */
}

대칭 암호 예제 (AES-CBC)

int aes_cbc_encrypt_af_alg(const void *key, int keylen,
    const void *iv, const void *pt, void *ct, int len)
{
    struct sockaddr_alg sa = {
        .salg_family = AF_ALG,
        .salg_type   = "skcipher",
        .salg_name   = "cbc(aes)",
    };
    int sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
    bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
    setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, key, keylen);

    int connfd = accept(sockfd, NULL, 0);

    /* cmsg로 IV와 operation(encrypt/decrypt) 전달 */
    struct msghdr msg = {};
    struct cmsghdr *cmsg;
    char cbuf[CMSG_SPACE(4) + CMSG_SPACE(16)];
    struct iovec iov = { .iov_base = (void *)pt, .iov_len = len };

    msg.msg_control = cbuf;
    msg.msg_controllen = sizeof(cbuf);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    /* cmsg 1: operation = ALG_OP_ENCRYPT */
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_ALG;
    cmsg->cmsg_type  = ALG_SET_OP;
    cmsg->cmsg_len   = CMSG_LEN(4);
    *(__u32 *)CMSG_DATA(cmsg) = ALG_OP_ENCRYPT;

    /* cmsg 2: IV */
    cmsg = CMSG_NXTHDR(&msg, cmsg);
    cmsg->cmsg_level = SOL_ALG;
    cmsg->cmsg_type  = ALG_SET_IV;
    cmsg->cmsg_len   = CMSG_LEN(20);  /* 4(ivlen) + 16(iv) */
    struct af_alg_iv *aiv = (void *)CMSG_DATA(cmsg);
    aiv->ivlen = 16;
    memcpy(aiv->iv, iv, 16);

    sendmsg(connfd, &msg, 0);
    read(connfd, ct, len);

    close(connfd);
    close(sockfd);
    return 0;
}

AEAD 예제 (AES-GCM)

/* AF_ALG AEAD: AES-256-GCM 암호화 */
struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type   = "aead",
    .salg_name   = "gcm(aes)",
};
int sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sockfd, (struct sockaddr *)&sa, sizeof(sa));
setsockopt(sockfd, SOL_ALG, ALG_SET_KEY, key, 32);

/* 인증 태그 크기 설정 (16바이트) */
setsockopt(sockfd, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 16);

int connfd = accept(sockfd, NULL, 0);

/* cmsg에 ALG_SET_OP, ALG_SET_IV, ALG_SET_AEAD_ASSOCLEN 설정 */
/* sendmsg: [AAD (assoclen bytes)] + [plaintext] */
/* read:    [AAD (assoclen bytes)] + [ciphertext] + [tag (16 bytes)] */

/* cmsg 3: AAD 길이 */
cmsg->cmsg_type = ALG_SET_AEAD_ASSOCLEN;
cmsg->cmsg_len  = CMSG_LEN(4);
*(__u32 *)CMSG_DATA(cmsg) = aad_len;

/* iov[0] = AAD, iov[1] = plaintext */
sendmsg(connfd, &msg, 0);
/* 수신: AAD + ciphertext + 16바이트 GCM 태그 */
read(connfd, outbuf, aad_len + pt_len + 16);

AF_ALG 성능 비교

작업 (8KB 블록)AF_ALGOpenSSL (EVP)비고
AES-256-CBC 암호화~1.2 GB/s~1.5 GB/sAF_ALG 소켓 오버헤드 존재
SHA-256 해시~1.8 GB/s~2.0 GB/s커널 전환 비용
SHA-256 (splice)~2.1 GB/szero-copy로 역전 가능
AES-GCM~1.0 GB/s~1.3 GB/sAEAD cmsg 오버헤드
ℹ️

AF_ALG vs OpenSSL: 소량 데이터에서는 소켓/커널 전환 오버헤드로 AF_ALG가 느리지만, splice() zero-copy나 QAT 등 전용 가속기를 활용할 때는 AF_ALG가 유리할 수 있습니다. AF_ALG의 주요 가치는 (1) 라이브러리 의존성 없는 커널 crypto 접근, (2) FIPS 모드 커널 모듈 직접 활용, (3) QAT/CCP 등 커널 전용 가속기 사용입니다.

/proc/crypto와 드라이버 선택

# ━━━ /proc/crypto 활용법 ━━━

# 특정 알고리즘의 모든 구현 확인
awk '/^name.*cbc\(aes\)/,/^$/' /proc/crypto
# name         : cbc(aes)
# driver       : cbc-aes-aesni       priority: 400
# name         : cbc(aes)
# driver       : cbc-aes-generic     priority: 100

# AF_ALG에서 특정 드라이버 강제 지정
# salg_name에 드라이버 이름을 직접 사용
struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type   = "skcipher",
    .salg_name   = "cbc-aes-aesni",  /* 드라이버 이름 직접 지정 */
};

# 전체 알고리즘 유형별 개수
grep '^type' /proc/crypto | sort | uniq -c | sort -rn
#  45 type         : skcipher
#  32 type         : shash
#  18 type         : aead
#   8 type         : ahash
#   5 type         : rng
💡

보안 주의: AF_ALG 소켓은 CAP_NET_ADMIN 없이도 사용할 수 있으므로, 일반 사용자가 커널 crypto를 호출할 수 있습니다. 커널 5.9+ 이후 crypto.fips_enabled=1 부트 파라미터가 설정되면 AF_ALG도 FIPS 인증 알고리즘만 사용 가능합니다.

AF_ALG 소켓 라이프사이클 socket() AF_ALG 생성 bind() type + name setsockopt() 키 설정 accept() 세션 생성 sendmsg() data + cmsg read() 결과 수신 커널 Crypto API tfm 할당 setkey encrypt/decrypt AES-NI / QAT / ARM CE 사용자 공간 소켓 호출이 커널 Crypto API의 tfm/request 작업으로 매핑됩니다 close(connfd) → close(sockfd) → tfm 해제
AF_ALG은 소켓 API를 통해 커널 Crypto API의 전체 기능을 사용자 공간에 노출합니다

암호화 오프로드 결정 가이드

다양한 암호화 오프로드 옵션 중 워크로드 특성에 맞는 방식을 선택하는 것이 중요합니다. CPU ISA 가속으로 충분한 경우 별도 가속기를 도입할 필요가 없고, 반대로 대규모 암호 처리에서는 전용 가속기가 CPU 자원을 확보해줍니다.

오프로드 유형별 선택 기준

오프로드 유형장점단점적합한 경우
CPU ISA
(AES-NI, ARM CE)
최저 지연, 추가 비용 없음, 모든 CPU에 내장 CPU 코어 점유, FPU 컨텍스트 전환 비용 대부분의 범용 워크로드, 소량~중량 암호화
PCI 가속기
(QAT, CCP)
CPU 부하 없음, 고처리량, 비대칭 암호 지원 DMA/MMIO 오버헤드, 비용, 드라이버 의존성 SSL/TLS 프록시, 대량 배치 암호화, PKI 연산
NIC 인라인
(IPsec/kTLS)
와이어 속도 처리, CPU 완전 해방, 라인 레이트 제한된 알고리즘, NIC 종속, SA 수 제한 고대역폭 네트워크 암호화 (100G+ IPsec/TLS)
스토리지 ICE
(UFS/eMMC/NVMe)
CPU 0%, 와이어 속도, 배터리 절약 제한된 알고리즘(AES-XTS), keyslot 수 제한 모바일 FBE, 서버 디스크 암호화

패킷 크기 임계값과 CPU 사용률

오프로드 효율은 데이터 크기에 크게 의존합니다. 소량 데이터에서는 오프로드 설정(DMA 맵핑, 디스크립터 전송)의 고정 비용이 암호화 자체보다 클 수 있습니다:

워크로드데이터 크기권장 방식근거
DNS/QUIC 패킷<512BCPU ISA (AES-NI/CE)오프로드 setup 비용 > 암호화 비용
웹 HTTP/2 TLS1~16 KBCPU ISA 또는 kTLS NICkTLS는 대역폭(Bandwidth) 높을 때 유리
IPsec VPN 터널1.4 KB (MTU)NIC offload패킷 수가 많아 CPU 부하 누적
디스크 I/O (4KB)4~128 KBICE 또는 CPU ISAICE 있으면 무조건 ICE 사용
SSL/TLS 프록시다양QAT / CCP수천 연결의 핸드셰이크 + 벌크 암호화
배치 파일 암호화MB~GBQAT / CCPCPU 코어를 다른 작업에 활용
ℹ️

일반적인 경험칙: (1) 스토리지 암호화는 ICE가 있으면 항상 ICE 사용, (2) 네트워크 암호화는 10G 이상에서 NIC offload 고려, (3) 그 외 대부분의 경우 AES-NI/ARM CE만으로 충분합니다. QAT/CCP 같은 PCI 가속기는 SSL 프록시나 VPN 게이트웨이처럼 암호화가 주 워크로드인 경우에 도입 효과가 큽니다.

종합 비교

특성CPU ISAPCI 가속기NIC 인라인스토리지 ICE
지연 시간최저 (~ns)중간 (~μs)낮음없음 (HW)
처리량~10 GB/s/코어~100 Gbps라인 레이트라인 레이트
CPU 부하높음없음없음없음
알고리즘 범위모든 알고리즘넓음제한적AES-XTS
추가 비용없음가속기 카드지원 NICSoC 내장
설정 복잡도자동드라이버 설정NIC + xfrminlinecrypt 옵션
대표 사례dm-crypt, TLSSSL 프록시IPsec 게이트웨이Android FBE
💡

SmartNIC/DPU 오프로드: NVIDIA BlueField, AMD Pensando 같은 DPU는 NIC 인라인 오프로드와 PCI 가속기의 특성을 결합합니다. IPsec packet offload + OVS 룰 처리를 DPU에서 수행하여 호스트 CPU를 완전히 해방할 수 있습니다. 상세는 SmartNIC/DPU 암호화 오프로드를 참고하세요.

암호화 오프로드 결정 플로차트 암호화 워크로드 분석 스토리지 암호화? Yes 스토리지 ICE UFS/eMMC/NVMe No 네트워크 10G+? Yes NIC 오프로드 IPsec/kTLS No 대량 배치 처리? Yes PCI 가속기 QAT / CCP No CPU ISA 가속 AES-NI / ARM CE 대부분의 범용 워크로드는 CPU ISA 가속(AES-NI/ARM CE)만으로 충분 — 특수 요구사항에만 전용 가속기 도입
워크로드 특성에 따라 적합한 오프로드 방식을 단계적으로 결정합니다

실전 구현 패턴과 다양한 예시

앞 절에서는 Crypto API의 각 계층과 가속 메커니즘을 나누어 보았지만, 실제 커널 코드는 거의 항상 여러 층을 동시에 건드립니다. 같은 AES라도 어떤 경로는 sleep 가능한 process context에서 동작하고, 어떤 경로는 softirq에서 바로 들어오며, 어떤 경로는 DMA 가능한 scatterlist와 함께 hardware queue에 실려 나갑니다. 따라서 실전 구현에서는 알고리즘 이름보다도 문맥 제약, 객체 수명, 입출력 버퍼 레이아웃, fallback 경로, 에러 전파를 먼저 설계해야 합니다.

아래 예시들은 단순히 "이 함수는 이렇게 호출한다"를 넘어서, 실제 서브시스템 코드가 어떤 틀로 조립되는지 보여 주는 데 초점을 둡니다. 특히 기존 문서의 개별 API 예시를 보완하기 위해, 이번 섹션은 소비자 관점에서 자주 필요한 패턴을 묶어서 설명합니다.

패턴핵심 API주로 쓰는 위치핵심 포인트
동기 대기 래퍼DECLARE_CRYPTO_WAIT, crypto_wait_req()fscrypt, 블록 계층, 제어 경로async 구현체도 같은 코드로 수용하되 sleep 가능한 문맥에서만 기다립니다.
다중 버퍼 해시crypto_ahash, scatterlist네트워크 패킷, 파일 검증, firmware blob연속 버퍼로 memcpy하지 않고 조각 난 데이터를 바로 해시합니다.
AEAD 레코드 처리crypto_aead, aead_request_set_ad()kTLS, IPsec ESP, 저장 포맷AAD, ciphertext, tag의 배치를 정확히 이해해야 합니다.
HMAC 기반 키 파생crypto_shash, hmac(sha256)세션 키 파생, 방향별 키 분리E2E 프로토콜은 보통 공유 비밀을 바로 쓰지 않고 다시 KDF에 통과시킵니다.
DRBG 직접 사용crypto_rngFIPS 경계, 재현 가능한 테스트일반 난수는 get_random_bytes(), 정책형 DRBG는 crypto_rng가 맞습니다.
압축 오프로드crypto_comp, crypto_acompzswap, 압축 가속기, storage pipeline동기/비동기 압축 API가 별도로 존재합니다.
서명 검증 파이프라인crypto_shash + crypto_sig모듈, 펌웨어, secure boot chain대부분의 서명 API는 메시지 본문이 아니라 digest를 입력으로 받습니다.
하드웨어 큐 드라이버crypto_engineSoC crypto 엔진, PCI 가속기큐잉, IRQ 완료, request 수명 관리를 프레임워크에 위임합니다.
객체 수명: tfm은 길게, request는 짧게, SG/IV는 작업 단위로 상위 객체 socket, inode, xfrm state, request queue 여기서 tfm을 오래 보관 tfm 알고리즘 인스턴스, 키 스케줄, fallback 연결/세션/디바이스 수명과 비슷 request IV, SG, callback, reqctx, DMA 상태 in-flight 작업마다 별도 필요 algorithm / engine generic, AES-NI, cryptd, DMA engine priority와 문맥 제약에 따라 선택 실무에서 중요한 수명 규칙 1. tfm을 패킷마다 alloc/free 하면 오버헤드와 contention이 커집니다. 2. request는 동시 비행 수만큼 필요하며, 완료 전 재사용하면 메모리 오염이 납니다. 3. SG, IV, wait 객체는 현재 작업이 끝날 때까지 살아 있어야 합니다.
Crypto API 소비자 코드에서 가장 많이 헷갈리는 부분은 "무슨 알고리즘을 고를까"보다 "어떤 객체를 얼마나 오래 살릴까"입니다.

비동기 구현을 동기처럼 다루는 skcipher 패턴

Crypto API의 많은 구현체는 내부적으로 비동기입니다. 하지만 상위 계층이 sleep 가능한 process context라면 굳이 콜백 기반 상태 머신으로 코드를 찢지 않고, DECLARE_CRYPTO_WAITcrypto_wait_req()로 "동기처럼" 다룰 수 있습니다. 이 패턴은 하드웨어 엔진이 있으면 비동기로 돌고, 없으면 generic 구현으로 즉시 끝나는 두 경우를 같은 코드로 수용한다는 점이 강점입니다.

#include <crypto/skcipher.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>

static int encrypt_ctr_two_segments(const u8 *key, unsigned int key_len,
                                    const u8 iv[16],
                                    const u8 *head, unsigned int head_len,
                                    const u8 *body, unsigned int body_len,
                                    u8 *out_head, u8 *out_body)
{
    struct crypto_skcipher *tfm;
    struct skcipher_request *req;
    struct scatterlist src[2], dst[2];
    u8 iv_local[16];
    unsigned int total = head_len + body_len;
    int ret;
    DECLARE_CRYPTO_WAIT(wait);

    tfm = crypto_alloc_skcipher("ctr(aes)", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    ret = crypto_skcipher_setkey(tfm, key, key_len);
    if (ret)
        goto out_free_tfm;

    req = skcipher_request_alloc(tfm, GFP_KERNEL);
    if (!req) {
        ret = -ENOMEM;
        goto out_free_tfm;
    }

    sg_init_table(src, 2);
    sg_set_buf(&src[0], head, head_len);
    sg_set_buf(&src[1], body, body_len);

    sg_init_table(dst, 2);
    sg_set_buf(&dst[0], out_head, head_len);
    sg_set_buf(&dst[1], out_body, body_len);

    memcpy(iv_local, iv, 16);
    skcipher_request_set_callback(req,
        CRYPTO_TFM_REQ_MAY_SLEEP | CRYPTO_TFM_REQ_MAY_BACKLOG,
        crypto_req_done, &wait);
    skcipher_request_set_crypt(req, src, dst, total, iv_local);

    ret = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);

    memzero_explicit(iv_local, sizeof(iv_local));
    skcipher_request_free(req);
out_free_tfm:
    crypto_free_skcipher(tfm);
    return ret;
}
포인트왜 중요한가실무 메모
crypto_wait_req()즉시 완료와 -EINPROGRESS/-EBUSY를 동일 경로로 수렴합니다.sleep 가능한 문맥에서만 사용해야 하며, atomic/softirq 경로에서는 기다리면 안 됩니다.
IV 복사본 사용운용 모드에 따라 IV를 워크 버퍼처럼 다루는 구현이 있어 원본 재사용이 위험합니다.요청마다 iv_local을 두고 완료 후 소거하는 습관이 안전합니다.
tfm은 장수 객체키 확장과 fallback 준비는 비쌉니다.socket/inode/queue 수명에 맞춰 캐시하고, 패킷마다 alloc/free 하지 않는 편이 좋습니다.
request는 in-flight 단위request 안에는 callback과 reqctx가 묶여 있으므로 동시에 두 작업에 재사용할 수 없습니다.동시 요청 수가 8개면 request도 최소 8개가 필요합니다.
CTR를 예시로 든 이유: 조각 난 버퍼를 그대로 처리하는 예시에서는 블록 정렬 제약이 적은 ctr(aes)가 설명하기 좋습니다. 같은 패턴을 xts(aes)에 적용하면 블록 계층, cbc(aes)에 적용하면 레거시 프로토콜 코드와 닮아집니다.

ahash와 멀티 세그먼트 scatterlist

shash는 작은 연속 버퍼에 가장 단순하지만, 실제 커널 데이터는 헤더와 payload, trailer가 제각각 다른 메모리 조각에 놓여 있는 경우가 많습니다. 이때 crypto_ahashscatterlist를 직접 받아 복사 없이 해시를 계산할 수 있고, DMA 기반 해시 엔진과도 자연스럽게 연결됩니다.

#include <crypto/hash.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>

static int calc_sha256_ahash_three_buffers(const u8 *hdr, unsigned int hdr_len,
                                          const u8 *payload, unsigned int payload_len,
                                          const u8 *tail, unsigned int tail_len,
                                          u8 digest[32])
{
    struct crypto_ahash *tfm;
    struct ahash_request *req;
    struct scatterlist sg[3];
    unsigned int total = hdr_len + payload_len + tail_len;
    int ret;
    DECLARE_CRYPTO_WAIT(wait);

    tfm = crypto_alloc_ahash("sha256", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    req = ahash_request_alloc(tfm, GFP_KERNEL);
    if (!req) {
        ret = -ENOMEM;
        goto out_free_tfm;
    }

    sg_init_table(sg, 3);
    sg_set_buf(&sg[0], hdr, hdr_len);
    sg_set_buf(&sg[1], payload, payload_len);
    sg_set_buf(&sg[2], tail, tail_len);

    ahash_request_set_callback(req,
        CRYPTO_TFM_REQ_MAY_SLEEP | CRYPTO_TFM_REQ_MAY_BACKLOG,
        crypto_req_done, &wait);
    ahash_request_set_crypt(req, sg, digest, total);

    ret = crypto_wait_req(crypto_ahash_digest(req), &wait);

    ahash_request_free(req);
out_free_tfm:
    crypto_free_ahash(tfm);
    return ret;
}

이 예시는 digest 단일 호출만 보여 주지만, 실전에서는 init/update/final 또는 finup 계열로 더 긴 스트림을 처리하기도 합니다. 예를 들어, 대형 펌웨어 이미지를 페이지 단위로 읽어 오면서 해시하려면 crypto_ahash_init() 후 페이지마다 crypto_ahash_update()를 호출하고, 마지막 조각에서 crypto_ahash_final()을 수행하는 식이 더 적합합니다.

선택 기준shashahash
입력 버퍼연속 버퍼 중심scatterlist 중심
문맥즉시 계산이 쉬운 제어 경로DMA 엔진, 큰 파일, 네트워크 조각 버퍼
완료 모델동기동기 또는 비동기
전형적 사례작은 키/메타데이터 HMAC패킷 본문, 페이지 캐시(Page Cache), 펌웨어 blob
SG 기반 해시가 좋은 이유: Scatter-Gather 문서의 Crypto API 절에서 설명한 것처럼, 네트워크 패킷의 헤더, frag list, trailer를 한 번에 묶을 수 있습니다. 중간에 연속 버퍼를 새로 만들지 않기 때문에 캐시 오염과 memcpy 오버헤드를 줄이고, H/W hash 엔진의 DMA 입력 형식과도 잘 맞습니다.

AEAD 레코드 처리: AAD, 암호문, 태그 배치

AEAD는 Crypto API에서 가장 자주 길이 계산을 틀리는 인터페이스입니다. 이유는 AAD 길이와 cryptlen이 서로 다른 의미를 갖기 때문입니다. aead_request_set_ad()는 AAD 길이를 별도로 주고, aead_request_set_crypt()cryptlen은 "암호화/복호화 대상 본문 길이"를 뜻합니다. 복호화 시에는 인증 태그까지 포함한 길이를 넘겨야 한다는 점도 실수 포인트입니다.

#include <crypto/aead.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>

static int gcm_encrypt_record_inplace(const u8 *key, unsigned int key_len,
                                      const u8 *iv, unsigned int iv_len,
                                      u8 *record, unsigned int aad_len,
                                      unsigned int plaintext_len)
{
    struct crypto_aead *tfm;
    struct aead_request *req;
    struct scatterlist sg;
    u8 iv_local[16];
    int ret;
    DECLARE_CRYPTO_WAIT(wait);

    tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    ret = crypto_aead_setkey(tfm, key, key_len);
    if (ret)
        goto out_free_tfm;

    ret = crypto_aead_setauthsize(tfm, 16);
    if (ret)
        goto out_free_tfm;

    req = aead_request_alloc(tfm, GFP_KERNEL);
    if (!req) {
        ret = -ENOMEM;
        goto out_free_tfm;
    }

    if (iv_len != crypto_aead_ivsize(tfm)) {
        ret = -EINVAL;
        goto out_free_req;
    }

    memcpy(iv_local, iv, iv_len);

    /* record 레이아웃: [AAD][plaintext][tag용 여유 16바이트] */
    sg_init_one(&sg, record, aad_len + plaintext_len + 16);
    aead_request_set_callback(req,
        CRYPTO_TFM_REQ_MAY_SLEEP | CRYPTO_TFM_REQ_MAY_BACKLOG,
        crypto_req_done, &wait);
    aead_request_set_ad(req, aad_len);
    aead_request_set_crypt(req, &sg, &sg, plaintext_len, iv_local);

    ret = crypto_wait_req(crypto_aead_encrypt(req), &wait);

    memzero_explicit(iv_local, sizeof(iv_local));
out_free_req:
    aead_request_free(req);
out_free_tfm:
    crypto_free_aead(tfm);
    return ret;
}
단계메모리 레이아웃cryptlen주의점
암호화 입력AAD || plaintextplaintext_lenAAD는 aead_request_set_ad()로 별도 지정합니다.
암호화 출력AAD || ciphertext || tag동일dst 버퍼 끝에 tag 공간이 미리 있어야 합니다.
복호화 입력AAD || ciphertext || tagciphertext_len + authsizetag까지 포함한 길이를 넘겨야 합니다.
복호화 출력AAD || plaintext동일태그 검증 실패 시 -EBADMSG가 대표적 오류입니다.
out-of-place AEAD의 함정: Crypto API는 출력 버퍼의 AAD 영역을 자동으로 채워 주지 않습니다. 즉, srcdst를 다르게 쓴다면 dst의 앞부분 AAD를 호출자가 직접 복사해 놓아야 합니다. 그래서 실무에서는 in-place 레코드 처리가 구현도 단순하고 버그도 적습니다.

kTLS, IPsec ESP, 일부 저장 포맷이 모두 이 레이아웃 감각 위에 서 있습니다. "왜 내 tag 검증이 항상 실패하지?"라는 문제는 대개 assoclen, cryptlen, tag 길이 중 하나를 잘못 넣었을 때 발생합니다. 특히 복호화에서 ciphertext_len만 넣고 tag 길이를 빼먹는 실수가 매우 흔합니다.

HMAC과 키 파생: 공유 비밀을 바로 쓰지 않는 이유

커널 안에서 암호를 다루다 보면 "공유 비밀이 나왔으니 바로 AES 키로 써도 되는가?"라는 질문이 자주 나옵니다. 보통은 그렇지 않습니다. ECDH 결과나 장치 고유 비밀은 길이, 분포, 방향 분리 문제 때문에 곧바로 데이터 키로 쓰지 않고, HMAC 기반 KDF를 한 번 더 거칩니다. 아래 예시는 hmac(sha256) 하나만으로 HKDF와 비슷한 구조를 구현해 TX/RX 키를 분리하는 패턴입니다.

#include <crypto/hash.h>
#include <linux/slab.h>
#include <linux/string.h>

static int hmac_sha256_once(const u8 *key, unsigned int key_len,
                            const u8 *msg, unsigned int msg_len,
                            u8 out[32])
{
    struct crypto_shash *tfm;
    struct shash_desc *desc;
    unsigned int dlen;
    int ret;

    tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    dlen = sizeof(*desc) + crypto_shash_descsize(tfm);
    desc = kmalloc(dlen, GFP_KERNEL);
    if (!desc) {
        crypto_free_shash(tfm);
        return -ENOMEM;
    }

    desc->tfm = tfm;
    ret = crypto_shash_setkey(tfm, key, key_len);
    if (!ret)
        ret = crypto_shash_digest(desc, msg, msg_len, out);

    memzero_explicit(desc, dlen);
    kfree(desc);
    crypto_free_shash(tfm);
    return ret;
}

static int derive_tx_rx_keys_from_secret(const u8 *salt, unsigned int salt_len,
                                         const u8 *shared, unsigned int shared_len,
                                         const u8 *info, unsigned int info_len,
                                         u8 tx_key[32], u8 rx_key[32])
{
    u8 prk[32];
    u8 t[32];
    u8 *block;
    int ret;

    block = kmalloc(32 + info_len + 1, GFP_KERNEL);
    if (!block)
        return -ENOMEM;

    /* Extract: PRK = HMAC(salt, shared_secret) */
    ret = hmac_sha256_once(salt, salt_len, shared, shared_len, prk);
    if (ret)
        goto out;

    /* Expand block 1: T1 = HMAC(PRK, info || 0x01) */
    memcpy(block, info, info_len);
    block[info_len] = 1;
    ret = hmac_sha256_once(prk, sizeof(prk), block, info_len + 1, t);
    if (ret)
        goto out;
    memcpy(tx_key, t, 32);

    /* Expand block 2: T2 = HMAC(PRK, T1 || info || 0x02) */
    memcpy(block, t, 32);
    memcpy(block + 32, info, info_len);
    block[32 + info_len] = 2;
    ret = hmac_sha256_once(prk, sizeof(prk),
                            block, 32 + info_len + 1, rx_key);
out:
    memzero_explicit(prk, sizeof(prk));
    memzero_explicit(t, sizeof(t));
    memzero_explicit(block, 32 + info_len + 1);
    kfree(block);
    return ret;
}
단계의미보안상 이점
Extractshared secret를 HMAC로 한 번 정규화해 PRK를 만듭니다.편향된 입력이나 길이 차이를 정리하고 salt 정책을 적용할 수 있습니다.
Expand block 1info || 0x01로 첫 번째 방향 키를 만듭니다.동일한 공유 비밀에서 파생되더라도 TX/RX 역할을 분리할 수 있습니다.
Expand block 2T1 || info || 0x02로 두 번째 방향 키를 만듭니다.키 재사용을 피하고, 반대 방향 트래픽이 같은 키를 쓰지 않게 합니다.
왜 이렇게 길게 도는가: WireGuard, TLS, IPsec 모두 세션 키를 만들 때 "키 합의 결과"와 "실제 데이터 키" 사이에 KDF 계층을 둡니다. Crypto API 소비자 코드도 이 구조를 따라가면, 나중에 알고리즘 교체나 키 길이 변경이 훨씬 수월해집니다.

crypto_rng로 DRBG를 직접 다루는 패턴

커널에서 일반적인 난수는 거의 항상 get_random_bytes()가 정답입니다. 하지만 FIPS 경계 안에서 특정 DRBG를 명시적으로 선택해야 하거나, 시험 벡터 재현처럼 랜덤 생성기 종류를 통제해야 하는 상황에서는 crypto_rng가 필요합니다.

#include <crypto/rng.h>
#include <linux/random.h>
#include <linux/string.h>

static int fill_nonce_from_drbg(u8 *out, unsigned int out_len)
{
    struct crypto_rng *drbg;
    u8 seed[64];
    int seed_len;
    int ret;

    drbg = crypto_alloc_rng("drbg_nopr_hmac_sha256", 0, 0);
    if (IS_ERR(drbg))
        return PTR_ERR(drbg);

    seed_len = crypto_rng_seedsize(drbg);
    if (seed_len < 0 || seed_len > sizeof(seed)) {
        ret = -EOVERFLOW;
        goto out_free_rng;
    }

    if (seed_len) {
        get_random_bytes(seed, seed_len);
        ret = crypto_rng_reset(drbg, seed, seed_len);
        if (ret)
            goto out_zero_seed;
    }

    ret = crypto_rng_get_bytes(drbg, out, out_len);

out_zero_seed:
    memzero_explicit(seed, sizeof(seed));
out_free_rng:
    crypto_free_rng(drbg);
    return ret;
}
언제 쓰나권장 API이유
일반 nonce, cookie, 임시 키get_random_bytes()커널 기본 CSPRNG를 가장 단순하고 안전하게 사용합니다.
특정 DRBG 정책 강제crypto_alloc_rng()알고리즘 이름과 seed 정책을 명시할 수 있습니다.
자가 테스트/재현 실험crypto_rng_reset()같은 seed로 같은 경로를 반복 검증하기 쉽습니다.
주의: DRBG에 주는 seed를 임의의 약한 값으로 채우면 오히려 전체 안전성이 낮아집니다. 운영 환경에서는 get_random_bytes()로 seed를 받아 DRBG를 초기화하는 것이 보통이며, seed와 내부 상태는 가능한 한 빨리 소거해야 합니다.

압축 API: crypto_comp와 crypto_acomp

Crypto Framework는 암호화뿐 아니라 압축도 함께 다룹니다. 이유는 커널 관점에서 "가속 가능한 데이터 변환"이라는 공통점이 크기 때문입니다. 특히 SoC 압축 엔진이나 PCI 가속기를 붙이는 드라이버는 암호화와 동일한 방식으로 algorithm 등록, request 큐잉, callback 완료를 구현합니다.

#include <linux/crypto.h>
#include <crypto/acompress.h>
#include <linux/scatterlist.h>

static int compress_lz4_sync(const u8 *src, unsigned int src_len,
                             u8 *dst, unsigned int *dst_len)
{
    struct crypto_comp *tfm;
    int ret;

    tfm = crypto_alloc_comp("lz4", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    ret = crypto_comp_compress(tfm, src, src_len, dst, dst_len);
    crypto_free_comp(tfm);
    return ret;
}

static int compress_deflate_async(struct scatterlist *src,
                                  struct scatterlist *dst,
                                  unsigned int src_len,
                                  unsigned int *dst_len)
{
    struct crypto_acomp *tfm;
    struct acomp_req *req;
    int ret;
    DECLARE_CRYPTO_WAIT(wait);

    tfm = crypto_alloc_acomp("deflate", 0, 0);
    if (IS_ERR(tfm))
        return PTR_ERR(tfm);

    req = acomp_request_alloc(tfm);
    if (!req) {
        ret = -ENOMEM;
        goto out_free_tfm;
    }

    acomp_request_set_callback(req,
        CRYPTO_TFM_REQ_MAY_SLEEP | CRYPTO_TFM_REQ_MAY_BACKLOG,
        crypto_req_done, &wait);
    acomp_request_set_params(req, src, dst, src_len, *dst_len);

    ret = crypto_wait_req(crypto_acomp_compress(req), &wait);
    if (!ret)
        *dst_len = req->dlen;

    acomp_request_free(req);
out_free_tfm:
    crypto_free_acomp(tfm);
    return ret;
}
구분특징어울리는 상황
crypto_comp동기, 연속 버퍼 기반짧은 제어 경로, 작은 메타데이터, 간단한 소프트웨어 압축
crypto_acomp비동기, SG 기반, callback 가능DMA 압축 엔진, 긴 데이터 스트림, 하드웨어 offload

압축 API를 굳이 Crypto Framework에 넣은 이유도 여기서 드러납니다. 상위 계층 입장에서는 "데이터 조각을 SG로 넘기고, 나중에 완료를 통지받는 변환 작업"이라는 점에서 암호화와 구조가 거의 같습니다. 그래서 storage stack이나 net stack이 암호 엔진과 압축 엔진을 비슷한 틀로 감쌀 수 있습니다.

펌웨어 검증 파이프라인: hash와 sig를 분리해서 조립

실제 검증 코드는 대개 "메시지 본문을 해시"한 뒤, 그 digest를 "서명 API로 검증"합니다. Crypto API의 crypto_sig는 이 경계를 분명히 드러내기 때문에, 검증 로직을 구현할 때도 해시 단계와 서명 단계가 자연스럽게 분리됩니다.

#include <crypto/hash.h>
#include <crypto/sig.h>
#include <linux/string.h>

static int verify_firmware_blob(const u8 *image, unsigned int image_len,
                                const u8 *sig, unsigned int sig_len,
                                const u8 *pubkey_der, unsigned int pubkey_der_len)
{
    struct crypto_sig *tfm;
    u8 digest[32];
    int ret;

    ret = calc_sha256(image, image_len, digest);
    if (ret)
        return ret;

    tfm = crypto_alloc_sig("ecdsa", 0, 0);
    if (IS_ERR(tfm)) {
        memzero_explicit(digest, sizeof(digest));
        return PTR_ERR(tfm);
    }

    ret = crypto_sig_set_pubkey(tfm, pubkey_der, pubkey_der_len);
    if (!ret)
        ret = crypto_sig_verify(tfm, sig, sig_len, digest, sizeof(digest));

    memzero_explicit(digest, sizeof(digest));
    crypto_free_sig(tfm);
    return ret;
}
단계역할실수 포인트
해시본문을 고정 길이 digest로 축약합니다.서명 알고리즘과 해시 정책을 상위 포맷에서 어떻게 선언하는지 놓치기 쉽습니다.
공개키 설정DER/BER 인코딩된 공개키를 tfm에 적재합니다.키 인코딩 형식이 맞지 않으면 알고리즘 자체는 정상이어도 검증이 실패합니다.
서명 검증digest와 서명값을 비교합니다.본문 전체를 넣는 것이 아니라 digest를 넣는 API라는 점을 자주 혼동합니다.
실제 적용 위치: 모듈 서명, 펌웨어 검증, secure boot 체인, 일부 IMA/EVM 경로는 결국 이 파이프라인의 변형입니다. 차이는 "누가 키를 공급하는가"와 "어떤 digest 정책을 쓰는가"에 가깝고, Crypto API 호출 구조 자체는 의외로 비슷합니다.

crypto_engine 기반 하드웨어 큐 드라이버 패턴

가속기 드라이버를 직접 작성할 때 가장 흔한 함정은 "요청 큐, 락, IRQ 완료, backlog 깨우기"를 전부 손으로 구현하려다가 request 수명 버그를 만드는 것입니다. 이때 crypto_engineCrypto API request를 디바이스 큐에 안전하게 넘기고, 완료 시 상위 계층으로 되돌리는 공통 뼈대를 제공합니다.

#include <crypto/engine.h>
#include <crypto/skcipher.h>

struct my_dev {
    struct skcipher_request *active_req;
};

struct my_tfm_ctx {
    struct crypto_engine *engine;
    struct my_dev *dd;
};

static int my_engine_do_one_request(struct crypto_engine *engine, void *areq)
{
    struct crypto_async_request *base = areq;
    struct skcipher_request *req =
        container_of(base, struct skcipher_request, base);
    struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
    struct my_tfm_ctx *ctx = crypto_skcipher_ctx(tfm);

    (void)engine;
    ctx->dd->active_req = req;

    /* 여기서 SG를 DMA 매핑하고, 레지스터를 적재한 뒤 HW 시작 */
    /* 예시는 설명을 위해 단일 active request만 추적합니다. */
    /* 완료는 IRQ에서 crypto_finalize_skcipher_request()로 통지 */
    return -EINPROGRESS;
}

static int my_skcipher_encrypt(struct skcipher_request *req)
{
    struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
    struct my_tfm_ctx *ctx = crypto_skcipher_ctx(tfm);

    return crypto_transfer_skcipher_request_to_engine(ctx->engine, req);
}

static irqreturn_t my_irq(int irq, void *data)
{
    struct my_dev *dd = data;
    struct my_tfm_ctx *ctx;
    int err = my_hw_status(dd);

    ctx = crypto_skcipher_ctx(crypto_skcipher_reqtfm(dd->active_req));

    /* DMA unmap, 상태 비트 정리 후 상위 요청 완료 통지 */
    crypto_finalize_skcipher_request(ctx->engine, dd->active_req, err);
    dd->active_req = NULL;
    return IRQ_HANDLED;
}
역할핵심 함수설명
요청 큐 입력crypto_transfer_skcipher_request_to_engine()상위 request를 엔진 큐에 넣고 backlog/순서를 관리합니다.
하드웨어 시작do_one_request 콜백드라이버가 DMA 매핑, 레지스터 설정, HW kick을 수행합니다.
완료 통지crypto_finalize_skcipher_request()IRQ/bottom-half에서 상위 Crypto API request를 깨웁니다.
엔진 수명crypto_engine_alloc_init(), crypto_engine_start()probe/remove 수명과 같이 관리합니다.
예시 범위: 위 코드는 request 전달과 완료 통지 흐름에 집중한 간략화 의사코드입니다. 실제 in-tree 드라이버는 큐 깊이, descriptor ring, prepare_cipher_request/unprepare_cipher_request, runtime PM, DMA 오류 복구까지 함께 다룹니다.

핵심은 request 자체를 상위 API가 소유하고, 드라이버는 그 request를 "빌려서" HW에 태우는 구조라는 점입니다. 따라서 드라이버는 request를 복제하려 들기보다, reqctx에 DMA 매핑 상태나 디스크립터 포인터만 저장하고 완료 후 정리하는 방식이 맞습니다. cra_ctxsize가 tfm 단위 상태라면, crypto_skcipher_set_reqsize()로 잡는 공간은 in-flight 작업 단위 상태입니다.

구현 시 자주 틀리는 지점

알고리즘 선택은 맞는데 실제 동작이 깨지는 경우, 아래 항목 중 하나에 걸린 경우가 많습니다. 이 표는 Crypto API를 소비하는 코드 관점의 체크리스트입니다. 앞서 나온 "알고리즘 구현 체크리스트"와는 성격이 다릅니다.

실수왜 문제인가수정 방향
패킷마다 tfm alloc/free키 스케줄, module ref, priority lookup 오버헤드가 누적됩니다.연결, inode, queue, state 객체에 tfm을 매달아 장기간 재사용합니다.
한 request를 동시 재사용callback/data/reqctx가 뒤섞여 UAF 또는 데이터 오염이 납니다.in-flight 요청 수만큼 request를 분리하거나 per-CPU 풀을 둡니다.
atomic 문맥에서 crypto_wait_req() 사용잠들 수 없는 경로에서 completion 대기를 걸어 deadlock 또는 경고가 납니다.atomic 경로는 진짜 비동기 콜백으로 처리하거나 cryptd 전용 경로를 씁니다.
AEAD tag 공간 미확보암호문 뒤 tag를 쓸 곳이 없어 메모리 파손이 납니다.암호화 출력 버퍼는 항상 plaintext + authsize 이상 확보합니다.
AEAD에서 assoclen/cryptlen 혼동태그 검증 실패가 나도 겉보기엔 키나 IV 문제처럼 보입니다.복호화에서는 cryptlen = ciphertext_len + authsize임을 기억합니다.
SG 조각을 억지로 memcpy해서 연속 버퍼화캐시 낭비와 추가 복사가 생기고 DMA offload 이점이 줄어듭니다.ahash, skcipher, aead의 SG 인터페이스를 그대로 활용합니다.
키/IV/PRK를 일반 kfree()만 하고 끝냄민감 값이 메모리에 남아 추후 관찰될 수 있습니다.memzero_explicit(), kfree_sensitive() 계열로 정리합니다.
드라이버 이름을 하드코딩해 강제 바인딩특정 CPU/보드에서만 동작하고 fallback이 깨질 수 있습니다.가급적 정규 이름(gcm(aes), sha256)을 사용하고, 디버깅 때만 드라이버 이름을 강제합니다.
fallback 경로 미검증HW 없는 장비나 softirq 경로에서만 터지는 버그가 숨어 있습니다.generic 구현, cryptd 경로, HW 가속 경로를 각각 나눠 테스트합니다.
/proc/crypto를 안 보고 체감으로만 판단실제로 어떤 구현이 선택됐는지 모르고 성능/오류를 추정하게 됩니다.name, driver, priority, async, selftest를 항상 같이 확인합니다.
검증 루틴 추천: 같은 코드 경로를 (1) 순수 소프트웨어 구현, (2) CPU ISA 가속, (3) 전용 하드웨어 가속기, (4) 인터럽트 문맥 fallback 순서로 각각 태워 보세요. Crypto API 버그는 "알고리즘 자체"보다 "문맥이 달라질 때 객체 수명이 깨지는지"에서 더 자주 드러납니다.

QAT 압축 가속 (crypto_comp / acomp)

Intel QAT는 암호화뿐 아니라 deflate, lz4 등의 데이터 압축/해제도 하드웨어로 가속합니다. 커널 Crypto API의 압축 인터페이스를 통해 zswap, 파일 시스템 압축, 네트워크 패킷 압축 등에서 CPU 부하를 대폭 줄일 수 있습니다.

crypto_comp vs crypto_acomp — 동기 vs 비동기 압축 API

커널은 두 가지 압축 API를 제공합니다. crypto_comp는 동기(Synchronous) 인터페이스로, 호출 즉시 결과를 반환하며 주로 소프트웨어 구현에서 사용됩니다. crypto_acomp는 비동기(Asynchronous) 인터페이스로, 하드웨어 가속기가 DMA를 통해 처리하고 완료 콜백으로 결과를 알려주는 방식입니다. QAT 압축 드라이버(qat_comp)는 비동기 acomp 인터페이스로 등록됩니다.

동기 (crypto_comp) 호출자 crypto_comp_compress() SW 구현 ← 블로킹 반환 비동기 (crypto_acomp) 호출자 crypto_acomp_compress() QAT HW 콜백 완료 ← 비블로킹 + DMA

acomp API 사용 패턴

비동기 압축 API(crypto_acomp)는 scatterlist 기반 입출력을 지원하며, QAT 하드웨어가 존재하면 자동으로 HW 가속 경로가 선택됩니다.

커널 소스 분석: crypto_acomp_compress() 흐름

/* 비동기 압축 API 사용 예시 */
struct crypto_acomp *acomp;
struct acomp_req *req;
struct crypto_wait wait;

/* 1. acomp tfm 할당 — QAT가 있으면 priority에 의해 HW 드라이버 선택 */
acomp = crypto_alloc_acomp("deflate", 0, 0);
if (IS_ERR(acomp))
    return PTR_ERR(acomp);

/* 2. request 할당 */
req = acomp_request_alloc(acomp);
acomp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
                           crypto_req_done, &wait);

/* 3. 입출력 파라미터 설정 (src, dst, src_len, dst_len) */
acomp_request_set_params(req, &src_sg, &dst_sg, slen, dlen);

/* 4. 압축 실행 — HW에서 DMA로 처리, 완료 시 콜백 호출 */
err = crypto_wait_req(crypto_acomp_compress(req), &wait);

/* 5. 결과: req->dlen에 압축된 크기가 저장됨 */
compressed_size = req->dlen;

/* 6. 정리 */
acomp_request_free(req);
crypto_free_acomp(acomp);
코드 설명
  • 핵심 crypto_alloc_acomp("deflate", 0, 0)/proc/crypto에서 가장 높은 priority를 가진 deflate 구현을 선택합니다. QAT 드라이버(qat_comp)가 로드되어 있으면 priority 4001로 소프트웨어 구현(priority 0)보다 우선됩니다.
  • 핵심 acomp_request_set_params()는 scatterlist 기반으로 입출력 버퍼를 설정합니다. QAT HW는 DMA를 사용하므로 별도의 CPU 복사 없이 메모리에서 직접 읽고 씁니다.
  • 핵심 crypto_acomp_compress()-EINPROGRESS를 반환할 수 있으며, 이 경우 crypto_wait_req()가 completion을 대기합니다. HW 큐가 포화되어 -ENOSPC가 반환되면 backlog 큐에 넣은 뒤 -EBUSY로 재시도를 알립니다.

Btrfs/ZFS 압축과 QAT 연동

파일 시스템의 투명 압축(Transparent Compression) 기능과 QAT의 연동 상태는 다음과 같습니다.

파일 시스템압축 알고리즘QAT 연동 가능 여부비고
Btrfs zstd, lzo, zlib 불가 (직접 연동 없음) Btrfs는 자체 압축 경로를 사용하며 Crypto API의 acomp를 경유하지 않음. workspace 기반 동기 압축만 지원
ZFS (OpenZFS) gzip, lz4, zstd 가능 (qat_compress 모듈) OpenZFS의 qat_compress / qat_checksum 모듈이 gzip, lz4 HW 가속을 제공. zfs set compression=gzip 시 QAT 자동 사용
zswap deflate, lz4, lzo, zstd 가능 (acomp 경로) 커널 6.x+에서 zswap이 acomp 인터페이스를 사용하므로 QAT deflate/lz4 자동 가속

NAS 백업 파이프라인에서 QAT 압축 활용

NAS 백업 시스템에서 QAT 압축을 활용하면 CPU를 거의 사용하지 않고 대용량 데이터를 실시간 압축할 수 있습니다. 특히 ZFS 기반 NAS에서는 qat_compress 모듈을 통해 투명하게 HW 압축이 적용됩니다. 또한 사용자 공간에서 qatzip 라이브러리를 통해 tar 아카이브 생성 시 QAT 가속을 적용할 수도 있습니다.

QAT 압축 성능 비교

방식처리량 (MB/s)CPU 사용률지연시간
SW deflate (single core) ~200 100% ~5ms
QAT deflate (C62x) ~10,000 <5% ~1ms
SW lz4 ~800 100% ~1ms
QAT lz4 ~20,000 <5% ~0.5ms
QAT 인스턴스와 동시 요청 수: QAT 디바이스 하나에는 여러 개의 Acceleration Engine(AE)이 존재하며, 각 AE에 Ring Pair가 할당됩니다. 동시 압축 요청 수는 사용 가능한 Ring Pair 수에 비례합니다. 예를 들어 QAT C62x는 최대 16개 Ring Pair를 제공하며, 4xxx(Sapphire Rapids)는 최대 64개까지 지원합니다. ServicesEnabled = dc로 압축 전용 모드로 설정하면 모든 AE를 압축에 할당하여 최대 처리량을 확보할 수 있습니다.

SED/OPAL 커널 인터페이스

SED(Self-Encrypting Drive)는 드라이브 내부 컨트롤러가 모든 데이터를 실시간으로 암호화/복호화하는 저장 장치입니다. CPU나 운영체제의 개입 없이 하드웨어 수준에서 전체 디스크 암호화(Full Disk Encryption, FDE)를 수행하므로 성능 저하가 전혀 없다는 것이 가장 큰 장점입니다. Linux 커널은 TCG Opal 2.0 표준을 구현한 SED 장치를 sed-opal 모듈로 지원합니다.

TCG Opal 2.0 표준

TCG(Trusted Computing Group)의 Opal 표준은 SED 디스크의 잠금/해제, 범위 관리, 인증 절차를 정의합니다. 핵심 개념은 다음과 같습니다.

sedutil / 사용자앱 IOC_OPAL_* ioctl sed-opal 모듈 Block Layer NVMe / SATA Security Command SED 드라이브 (Opal 2.0) AES-256 HW 엔진 + MEK

Linux 커널 SED/OPAL 지원

커널의 sed-opal 모듈(CONFIG_BLK_SED_OPAL)은 블록 디바이스 레이어에서 TCG Opal 명령을 NVMe Security Send/Receive 또는 ATA Trusted Send/Receive 명령으로 변환합니다. 사용자 공간은 ioctl() 시스템 호출을 통해 SED 기능을 제어합니다.

# 커널 설정 확인
grep CONFIG_BLK_SED_OPAL /boot/config-$(uname -r)
# CONFIG_BLK_SED_OPAL=y

# Opal 지원 여부 확인 (NVMe)
nvme id-ctrl /dev/nvme0 | grep oacs
# oacs: 0x17  ← bit 0 (Security Send/Receive) 설정됨

# sedutil로 디스크 상태 확인
sedutil-cli --query /dev/nvme0n1

핵심 ioctl 인터페이스

커널이 제공하는 주요 IOC_OPAL_* ioctl 명령은 다음과 같습니다.

ioctl기능설명
IOC_OPAL_SAVE 비밀번호 저장 커널 키링에 Opal 비밀번호를 저장하여 suspend/resume 시 자동 잠금 해제에 사용
IOC_OPAL_LOCK_UNLOCK 잠금/해제 특정 Locking Range의 읽기/쓰기 잠금 상태를 변경
IOC_OPAL_ACTIVATE_LSP Locking SP 활성화 SED 기능을 처음 활성화. 이후 Locking Range 설정이 가능해짐
IOC_OPAL_SET_PW 비밀번호 설정 Admin 또는 User 비밀번호를 설정/변경
IOC_OPAL_REVERT_TPR 공장 초기화 PSID를 사용하여 드라이브를 공장 상태로 되돌림 (모든 데이터 삭제)
IOC_OPAL_ERASE_LR Locking Range 삭제 특정 범위의 MEK를 재생성하여 해당 범위의 데이터를 암호학적으로 삭제

커널 소스 분석: opal_lock_unlock() 커널 내부 흐름

/* block/sed-opal.c — Opal 잠금/해제 핵심 경로 */
static int opal_lock_unlock(struct opal_dev *dev,
                            struct opal_lock_unlock *lk_unlk)
{
    /* 1. Opal 세션 시작 — Admin SP 또는 Locking SP에 인증 */
    const struct opal_step unlock_steps[] = {
        { start_auth_opal_session, &lk_unlk->session },
        { lock_unlock_locking_range, lk_unlk },
        { end_opal_session, NULL },
    };

    /* 2. 단계별 실행 — TCG Opal 프로토콜 패킷 전송 */
    return execute_steps(dev, unlock_steps,
                        ARRAY_SIZE(unlock_steps));
}

/* lock_unlock_locking_range() 내부 */
static int lock_unlock_locking_range(struct opal_dev *dev, void *data)
{
    /* Locking Range N의 ReadLocked/WriteLocked 속성을 Set 메서드로 변경 */
    /* OPAL_LOCKING_RANGE + range_id → 대상 UID 결정 */
    /* l_state: OPAL_RO (읽기 전용), OPAL_RW (읽기/쓰기), OPAL_LK (잠금) */
    add_token_u8(&err, dev, OPAL_STARTNAME);
    add_token_u8(&err, dev, OPAL_VALUES);
    /* ReadLocked = (l_state != OPAL_RW && l_state != OPAL_RO) */
    /* WriteLocked = (l_state != OPAL_RW) */
    ...
}
코드 설명
  • 핵심 opal_lock_unlock()은 3단계 프로토콜로 동작합니다: (1) Opal 세션을 시작하고 비밀번호로 인증, (2) Locking Range의 ReadLocked/WriteLocked 속성을 변경, (3) 세션을 종료합니다.
  • 핵심 execute_steps()는 각 단계를 순차적으로 실행하며, NVMe Security Send/Receive 명령을 통해 드라이브와 통신합니다. TCG Opal 프로토콜은 토큰 기반 바이너리 포맷을 사용하며, add_token_u8() 등으로 패킷을 구성합니다.
  • 핵심 잠금 상태(l_state)는 OPAL_RO(읽기 전용), OPAL_RW(읽기/쓰기 허용), OPAL_LK(완전 잠금) 세 가지가 있습니다. suspend/resume 시 커널은 IOC_OPAL_SAVE로 저장된 비밀번호를 사용하여 자동 잠금 해제를 수행합니다.

sedutil 사용자 공간 도구

sedutil-cli는 SED/Opal 디스크를 관리하는 오픈소스 도구입니다. 초기 설정부터 잠금/해제, 비밀번호 변경, 공장 초기화까지 모든 Opal 작업을 수행할 수 있습니다.

# 1. 초기 설정 — SID 비밀번호 설정 + Locking SP 활성화 + MBR 섀도잉
sedutil-cli --initialsetup password /dev/nvme0n1

# 2. Locking Range 0 (전체 디스크) 잠금 활성화
sedutil-cli --enableLockingRange 0 password /dev/nvme0n1

# 3. 디스크 잠금 해제
sedutil-cli --setLockingRange 0 RW password /dev/nvme0n1

# 4. 디스크 잠금 (재부팅 시 자동 잠금과 동일)
sedutil-cli --setLockingRange 0 LK password /dev/nvme0n1

# 5. 비밀번호 변경
sedutil-cli --setSIDPassword old_password new_password /dev/nvme0n1
sedutil-cli --setAdmin1Pwd old_password new_password /dev/nvme0n1

# 6. 공장 초기화 (PSID 사용 — 드라이브 라벨 확인)
# ⚠️ 모든 데이터가 영구 삭제됩니다
sedutil-cli --PSIDrevert PSID_FROM_LABEL /dev/nvme0n1

SED vs dm-crypt vs blk-crypto 비교

방식암호화 위치CPU 부하성능 영향키 관리
dm-crypt (LUKS) 블록 레이어 (SW) 높음 (AES-NI 시 중간) 10-30% 감소 dm-crypt / LUKS 키슬롯
blk-crypto 인라인 HW 엔진 없음 0-2% 감소 blk-crypto keyslot manager
SED/OPAL 드라이브 내부 컨트롤러 없음 0% (투명) TCG Opal (드라이브 자체 관리)

NAS 환경에서 SED는 제로 성능 손실로 전체 디스크 암호화를 제공하는 가장 효율적인 방법입니다. 다수의 NVMe SSD를 사용하는 NAS에서 dm-crypt의 CPU 오버헤드를 제거할 수 있으며, 드라이브 교체나 폐기 시 IOC_OPAL_ERASE_LR을 통해 MEK를 재생성하면 즉시 암호학적 삭제(Crypto Erase)가 가능합니다.

SED 비밀번호 분실 주의: SED/Opal 비밀번호를 분실하면 디스크의 데이터를 복구할 방법이 없습니다. 유일한 복구 방법은 드라이브 라벨에 인쇄된 PSID(Physical Security ID)를 사용한 공장 초기화(PSIDrevert)이며, 이 경우 MEK가 재생성되어 모든 데이터가 영구적으로 삭제됩니다. 운영 환경에서는 비밀번호를 안전한 곳에 백업하고, IOC_OPAL_SAVE를 통해 커널 키링에 저장하여 suspend/resume 시 자동 잠금 해제를 설정하세요.

HSM (Hardware Security Module, 하드웨어 보안 모듈)

HSM(Hardware Security Module)은 암호화 키의 생성·저장·사용을 물리적으로 격리된 전용 하드웨어에서 수행하는 보안 장치입니다. 일반 서버나 소프트웨어에서는 개인키가 메모리에 노출되어 추출 위험이 존재하지만, HSM은 키가 장치 내부를 절대 떠나지 않도록 설계됩니다. FIPS 140-2/140-3 Level 3 이상의 인증을 갖추며, 물리적 탬퍼 감지(tamper detection) 및 탬퍼 대응(tamper response) 메커니즘으로 키를 보호합니다.

HSM vs TPM vs 소프트웨어 키 저장소

특성HSMTPM소프트웨어 키스토어
키 저장 위치전용 하드웨어 (FIPS 인증)SoC/마더보드 내장 칩디스크/메모리
키 추출 가능 여부불가 (탬퍼 시 자동 삭제)불가 (제한적 연산만 지원)가능 (메모리 덤프 위험)
암호 연산 성능수만~수십만 TPS수십~수백 TPSCPU 의존
FIPS 인증 등급Level 2~4Level 1~2Level 1 (소프트웨어)
폼 팩터PCIe 카드 / 네트워크 어플라이언스 / USBSPI/I2C 칩 (2×3mm)해당 없음
비용수백~수천만 원수천~수만 원무료
주요 용도CA 루트키, 코드 서명, 결제, PKI플랫폼 무결성, Measured Boot개발/테스트
다중 테넌트파티션/슬롯으로 격리단일 소유자파일 권한

HSM 내부 아키텍처

HSM은 외부 환경과 완전히 격리된 보안 경계(Security Boundary) 내에서 동작합니다. 모든 암호화 연산은 전용 프로세서에서 수행되며, 키 자료(Key Material)는 보안 경계를 벗어나지 않습니다. 호스트 시스템은 PKCS#11, KMIP, 또는 벤더 전용 API를 통해 HSM에 연산을 요청하고 결과만 받습니다.

HSM 내부 아키텍처 호스트 시스템 애플리케이션 (OpenSSL, Java, nginx) PKCS#11 / KMIP / 벤더 API HSM 드라이버 (PCIe / USB / 네트워크) PCIe / USB / Network HSM 보안 경계 (Security Boundary) 탬퍼 감지/대응 (온도·전압·개봉·관통 센서 → 키 즉시 삭제) 보안 프로세서 대칭키 엔진 (AES, 3DES, SM4) 비대칭키 엔진 (RSA, ECC, SM2) 해시/HMAC (SHA-2/3, SM3) 키 저장소 (배터리 백업 SRAM) 마스터 키 (Master Key / KEK) 파티션별 키 슬롯 (래핑된 상태) 인증서 / 공개키 객체 TRNG (True RNG) 펌웨어 (서명 검증) Secure Boot Chain 감사 로그 (Audit Log) 변조 불가 기록 PKCS#11 연산 흐름 C_SignInit() 메커니즘+키 핸들 C_Sign(data) 데이터 → HSM 전송 HSM 내부 서명 수행 키 노출 없이 결과만 반환 키 생명주기 (Key Lifecycle) 생성 TRNG 기반 활성 서명/암호화 사용 백업 KEK 래핑 내보내기 폐기 제로화(Zeroize) 모든 키 자료는 보안 경계 내부에서만 평문 상태로 존재합니다 외부 백업 시 반드시 KEK(Key Encryption Key)로 래핑된 암호문으로 내보냅니다
HSM은 보안 경계 내부에 키 저장소·암호 엔진·TRNG·감사 로그를 통합하며, 키는 보안 경계를 벗어나지 않습니다

FIPS 140-2/140-3 보안 등급

HSM의 보안 등급은 미국 NIST의 FIPS 140 표준으로 정의됩니다. 대부분의 상용 HSM은 Level 3 인증을 갖추고 있으며, 금융·군사·정부 기관에서는 Level 3 이상을 요구합니다.

등급물리 보안 요구사항키 관리환경 방어적용 예
Level 1 생산 등급 장비, 탬퍼 증거 없음 소프트웨어 기반 허용 없음 소프트웨어 암호 모듈
Level 2 탬퍼 증거 (봉인 씰, 코팅) 역할 기반 인증 필수 없음 TPM, 기본 HSM
Level 3 탬퍼 감지+대응 (개봉 시 키 삭제) ID 기반 인증 필수 없음 Thales Luna, Utimaco, Entrust nShield
Level 4 Level 3 + 능동적 물리 방어 다중 인자 인증 전압·온도 변조 감지 군사·정부 최고 등급
ℹ️

FIPS 140-2 vs 140-3: FIPS 140-3(2019년 발표, ISO 19790 기반)은 140-2를 대체하는 최신 표준입니다. 주요 변경점은 (1) 소프트웨어 모듈에 대한 테스트 강화, (2) 비결정적 난수 생성기(NDRNG) 요구, (3) 조건부 자체 테스트(Conditional Self-Test) 의무화입니다. 2026년 현재 대부분의 신규 HSM은 FIPS 140-3 인증을 취득하고 있습니다.

주요 HSM 제품군

제조사 / 제품폼 팩터FIPS 등급RSA-2048 서명/초파티션인터페이스Linux 지원
Thales Luna Network HSM 7네트워크 어플라이언스 (1U)140-3 L320,000최대 100Ethernet (NTLS/TLS)PKCS#11 라이브러리
Thales Luna PCIe HSM 7PCIe 카드140-3 L320,000최대 20PCIe 3.0 x4커널 모듈 + PKCS#11
Entrust nShield Connect XC네트워크 어플라이언스(1U)140-2 L314,400다중 Security WorldEthernetPKCS#11 / JCE / CAPI
Utimaco CryptoServer Se Gen2PCIe 카드140-2 L315,000다중 펌웨어 파티션PCIe 3.0PKCS#11 / JCE
AWS CloudHSM클라우드 (전용 하드웨어)140-2 L3~1,100VPC 격리ENI (네트워크)PKCS#11 / OpenSSL
Azure Managed HSM클라우드 (전용 풀)140-2 L3가변구독 격리REST API / SDKaz CLI / SDK
YubiHSM 2USB (나노 크기)140-2 L3~100최대 16 인증 키USB 2.0PKCS#11 / yubihsm-shell
Nitrokey HSM 2USB 스마트카드CC EAL5+~50최대 48 키 슬롯USB (PC/SC)PKCS#11 (OpenSC)

Linux 커널과 HSM 연동

리눅스 커널은 HSM에 직접적으로 의존하지 않지만, 여러 서브시스템이 HSM과 연동됩니다. 커널 모듈 서명, 커널 키링(Keyring), PKCS#11을 통한 TLS 인증서 관리 등이 대표적입니다.

Linux와 HSM 연동 구조 사용자 공간 (User Space) OpenSSL ENGINE / PROVIDER GnuTLS / NSS PKCS#11 모듈 Java (JCE) SunPKCS11 Provider NGINX / HAProxy TLS 개인키 보호 HashiCorp Vault Auto-Unseal / 루트키 PKCS#11 표준 인터페이스 (libpkcs11.so / p11-kit) 커널 공간 (Kernel Space) 커널 키링 (Keyring) .builtin_trusted_keys 모듈 서명 검증 scripts/sign-file (빌드 시) IMA/EVM 파일 무결성 서명 dm-verity / dm-crypt 블록 디바이스 무결성/암호화 Linux Crypto API (crypto_register_alg / crypto_alloc_tfm) ioctl / syscall 하드웨어 PCIe HSM Luna, nShield, Utimaco USB HSM YubiHSM, Nitrokey 네트워크 HSM Luna Network, nShield 클라우드 HSM AWS / Azure / GCP TPM 2.0 Infineon, Nuvoton, STMicro
사용자 공간 애플리케이션은 PKCS#11을 통해 HSM에 접근하고, 커널은 모듈 서명·IMA·dm-verity 등에서 HSM 기반 키를 활용합니다

PKCS#11과 HSM 활용

PKCS#11(Cryptoki)은 OASIS(구 RSA Laboratories) 표준으로, 암호화 토큰(HSM, 스마트카드, TPM)에 대한 플랫폼 독립적 C API를 정의합니다. 리눅스에서 PKCS#11은 HSM과 소통하는 사실상의 표준 인터페이스이며, p11-kitp11-kit-proxy를 통해 여러 HSM 벤더 모듈을 통합 관리할 수 있습니다.

# ━━━ PKCS#11 기본 설정 ━━━

# p11-kit 설치 (여러 PKCS#11 모듈 통합 관리)
sudo apt install p11-kit p11-kit-modules libengine-pkcs11-openssl

# HSM 벤더 PKCS#11 모듈 등록 (예: Thales Luna)
cat > /etc/pkcs11/modules/luna.module <<EOF
module: /usr/lib/libCryptoki2_64.so
EOF

# 등록된 토큰/슬롯 확인
p11tool --list-tokens
# Token 0:
#   URL: pkcs11:model=Luna%20K7;manufacturer=SafeNet;serial=01234567;token=production
#   Type: Hardware token
#   Flags: RNG, Login required

# 토큰 내 키/인증서 목록
p11tool --login --list-all "pkcs11:token=production"
# Object 0:
#   URL: pkcs11:...;object=tls-server-key;type=private
#   Type: Private key (RSA-2048)
#   ID: 01:23:45:67:89:ab

OpenSSL + HSM (PKCS#11 ENGINE / PROVIDER)

# ━━━ OpenSSL 1.x: PKCS#11 ENGINE 사용 ━━━

# HSM 내부에서 RSA-2048 키 쌍 생성 (키가 HSM을 떠나지 않음)
pkcs11-tool --module /usr/lib/libCryptoki2_64.so \
  --login --pin "****" \
  --keypairgen --key-type rsa:2048 \
  --label "tls-server-key" --id 01

# CSR 생성 (OpenSSL ENGINE으로 HSM 키 참조)
openssl req -new -engine pkcs11 \
  -keyform engine \
  -key "pkcs11:token=production;object=tls-server-key;type=private" \
  -subj "/CN=web.example.com" \
  -out server.csr

# HSM 키로 인증서 서명
openssl x509 -req -engine pkcs11 \
  -signkey "pkcs11:token=ca;object=ca-root-key;type=private" \
  -in server.csr -out server.crt -days 365

# ━━━ OpenSSL 3.x: PKCS#11 PROVIDER 사용 ━━━

# openssl.cnf에 provider 등록
# [openssl_init]
# providers = provider_sect
# [provider_sect]
# pkcs11 = pkcs11_sect
# [pkcs11_sect]
# module = /usr/lib/ossl-modules/pkcs11.so
# pkcs11-module-path = /usr/lib/libCryptoki2_64.so
# activate = 1

openssl pkey -provider pkcs11 \
  -in "pkcs11:token=production;object=tls-server-key;type=private" \
  -pubout -out server-pub.pem

NGINX + HSM (TLS 개인키 보호)

# ━━━ nginx.conf — HSM에 저장된 개인키로 TLS 종단 ━━━

# OpenSSL ENGINE 방식 (OpenSSL 1.x)
ssl_engine pkcs11;

server {
    listen 443 ssl;
    server_name web.example.com;

    ssl_certificate     /etc/nginx/certs/server.crt;

    # 개인키를 파일 대신 PKCS#11 URI로 지정
    # → 키가 HSM 내부에 있으므로 서버 침해 시에도 키 추출 불가
    ssl_certificate_key "engine:pkcs11:pkcs11:token=production;object=tls-server-key;type=private;pin-value=****";

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
}

커널 모듈 서명에 HSM 활용

# ━━━ 커널 모듈 서명 키를 HSM에 보관 ━━━

# 1. HSM 내부에서 서명 키 생성
pkcs11-tool --module /usr/lib/libCryptoki2_64.so \
  --login --pin "****" \
  --keypairgen --key-type rsa:4096 \
  --label "kernel-module-signing" --id 10

# 2. 공개키만 추출 (DER 형식)
pkcs11-tool --module /usr/lib/libCryptoki2_64.so \
  --login --pin "****" \
  --read-object --type pubkey --label "kernel-module-signing" \
  -o signing_key_pub.der

# 3. 커널 빌드 설정
# .config:
# CONFIG_MODULE_SIG=y
# CONFIG_MODULE_SIG_SHA512=y
# CONFIG_MODULE_SIG_KEY="pkcs11:token=build;object=kernel-module-signing;type=private"
# CONFIG_SYSTEM_TRUSTED_KEYS="certs/signing_key_pub.pem"

# 4. sign-file로 HSM 키를 사용하여 모듈 서명
scripts/sign-file sha512 \
  "pkcs11:token=build;object=kernel-module-signing;type=private" \
  certs/signing_key_pub.pem \
  drivers/example/example.ko

# 5. 서명 확인
modinfo drivers/example/example.ko | grep sig
# sig_id:         PKCS#7
# sig_hashalgo:   sha512
# sig_key:        AB:CD:EF:...

키 세레모니 (Key Ceremony)

HSM에 루트 CA 키를 생성하는 절차를 키 세레모니(Key Ceremony)라 합니다. 이 과정은 다수의 인증된 참여자가 물리적으로 입회하여 진행하며, 전체 과정이 영상 녹화되고 공증됩니다. 키 세레모니는 다음 단계로 구성됩니다.

키 세레모니(Key Ceremony) 절차 1. 환경 준비 에어갭 시스템 부팅 HSM 초기화 (공장 상태) 2. 관리자 인증 M-of-N 스마트카드 삽입 다중 관리자 PIN 입력 3. 키 생성 HSM TRNG 기반 RSA/ECC 키 쌍 HSM 내부에서 생성 4. 루트 인증서 서명 자체 서명 루트 CA 생성 유효기간 20~30년 5. 백업 및 봉인 KEK 래핑 백업 → 금고 감사 로그 공증 M-of-N 분할 인증 (Quorum Authentication) 관리자 A 스마트카드 #1 + PIN 관리자 B 스마트카드 #2 + PIN 관리자 C 스마트카드 #3 + PIN ... 관리자 N 스마트카드 #N (대기) HSM 잠금 해제 M명 이상 인증 시에만 키 연산 허용 예: 5명 중 3명(3-of-5) 인증 필요 — 단일 관리자의 키 탈취로는 HSM 접근 불가
키 세레모니는 에어갭 환경에서 M-of-N 다중 관리자 인증을 거쳐 루트 키를 생성하며, 전 과정이 감사 로그로 기록됩니다

HSM 고가용성과 백업

HSM은 단일 장애점(Single Point of Failure)이 될 수 있으므로, 프로덕션 환경에서는 반드시 고가용성(HA) 구성과 키 백업 전략이 필요합니다.

전략방식장점주의사항
HA 그룹 (Active-Active) 동일 키를 복수 HSM에 복제, 로드밸런서가 분배 무중단 서비스, 자동 페일오버 동기화 지연 시 키 불일치 위험
KEK 래핑 백업 마스터 키(KEK)로 암호화된 키 블롭을 외부 저장 HSM 장애 시 다른 HSM에 복원 가능 KEK 자체의 안전한 분산 보관 필수
클로닝 (동일 모델) 동일 벤더·모델 HSM 간 전체 키 복제 완전한 동기화 보장 벤더 종속적, 동일 모델만 가능
M-of-N 분할 백업 마스터 키를 N개 조각으로 분할, M개 모으면 복원 단일 백업 조각 탈취 무효화 관리자 간 협조 필수, 분실 위험
# ━━━ Thales Luna HA 그룹 설정 예시 ━━━

# HA 그룹 생성 (2개 HSM 파티션을 HA 쌍으로 구성)
lunacm:> hagroup creategroup \
  -label "production-ha" \
  -slot 1 \
  -password "****"

# 두 번째 HSM을 HA 멤버로 추가
lunacm:> hagroup addmember \
  -group "production-ha" \
  -slot 2 \
  -password "****"

# HA 그룹 상태 확인
lunacm:> hagroup listgroups
#   HA Group Label:  production-ha
#   HA Group Number: 1
#   HA Group Slot:   6
#   Sync Status:     Yes (All members synchronized)
#   Members:
#     Slot 1 (hsm-primary.internal) — Online
#     Slot 2 (hsm-secondary.internal) — Online

# ━━━ 키 백업 (KEK 래핑) ━━━

# 백업 HSM에 키 블롭 내보내기
lunacm:> partition backup \
  -slot 1 \
  -password "****" \
  -backupTokenSlot 3

# 백업에서 복원 (다른 Luna HSM)
lunacm:> partition restore \
  -slot 4 \
  -password "****" \
  -backupTokenSlot 3

HSM 주요 활용 사례

HSM 운영 시 주의사항: HSM은 강력한 보안을 제공하지만, 잘못된 운영은 서비스 장애로 직결됩니다. (1) M-of-N 스마트카드를 모두 분실하면 HSM 키에 영구적으로 접근 불가합니다 — 반드시 지리적으로 분산 보관하세요. (2) HSM 펌웨어 업데이트는 키 백업 후 진행하며, 업데이트 실패 시 공장 초기화될 수 있습니다. (3) 네트워크 HSM은 mTLS(상호 TLS)로 통신을 보호하고, 접근 IP를 방화벽으로 제한하세요. (4) 감사 로그를 정기적으로 SIEM에 수집하여 비인가 접근 시도를 모니터링하세요.

Crypto Framework (Crypto API)와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.

외부 참고 자료