Programs and patches
CryptoDev for Linux
A Linux port of the /dev/crypto interface.
Last updated · 27 November 2009
Device /dev/crypto (aka CryptoDev) is a way for userspace processes to use cryptographic algorithms provided by kernel CryptoAPI modules. For example a process that needs to AES-encrypt some data can either:
- Have the AES algorithm built in, or
- Use an external library like OpenSSL that will do the encryption, or
- Ask the kernel to do the encryption
Although in most cases using the external library for cryptographic tasks would be the best option, sometimes it may come handy to have a kernel interface for it as well. Using it one may create a really lightweight programs (see cryptodev-demo.c) supporting cryptography without any additional libraries.
The CryptoDev was originally introduced in OpenBSD (at least as far as I know :-), later ported to FreeBSD and on this page you can find my port for Linux 2.6. Because kernel internals differ a lot between BSD and Linux I only attempted to keep the API (i.e. the semantics used when talking with /dev/crypto) and wrote the driver itself from scratch.
Linux kernel patches
To enable /dev/crypto device patch your kernel with the
following patch and configure with
Linux kernel 2.6.27+
- cryptodev-20091126.tar.bz2
- Standalone module,
doesn't require kernel patching, simply unpack and
run
make && make install. - Known to work with 2.6.32 and probably later kernels as well.
- Thanks Nikos Mavrogiannopoulos for the port to 2.6.27
Linux kernel 2.6.8
The module takes two parameters:
- verbosity=
- This is 0 by default, i.e. no messages about CryptoDev usage are printed. Use 1 if you are developing or using a program that accesses /dev/crypto and is having problems with it. This way some detailed error messages will be print if things go wrong. If you really want to have your log full of messages about CryptoDev usage, set verbosity to 2.
- enable_stats=
- The driver can collect statistics about CryptoDev usage. For now it can tell you how many bytes was encrypted or decrypted during each session, what size had the biggest request and what was the average size per request. By default this is 0, i.e. no statistics are collected. Set enable_stats=1 and verbosity=2 if you want to be getting the statistics described above.
The parameters can be changed even when the module is already loaded by writing to /sys/module/cryptodev/{enable_stats,verbosity}.
Userspace demo
The basic structure of a program using CryptoDev is following:
fd = open("/dev/crypto");
ioctl(fd, CIOCGSESSION);
ioctl(fd, CIOCRYPT);
[... repeat CIOCRYPT ioctls ...]
ioctl(fd, CIOCFSESSION);
close(fd);
On OpenBSD it is needed to clone the filedescriptor before actually using it. This is also supported on Linux, but not required:
fd = open("/dev/crypto");
ioctl(fd, CRIOGET, &fd_new);
ioctl(fd_new, CIOCGSESSION);
ioctl(fd_new, CIOCRYPT);
[... repeat CIOCRYPT ioctls ...]
ioctl(fd_new, CIOCFSESSION);
close(fd_new);
close(fd);
Indeed, there are some structures floating around, but the for the overview this is enough. See the following demo for details:
- CryptoDev usage demo
- Read cryptodev-demo1.c below · Download raw cryptodev-demo1.c
- CryptoDev with OpenSSL
- Read cryptodev-demo2.c below · Download raw cryptodev-demo2.c
CryptoDev support for OpenSSL
If you want to combine the (dis:-)advantages of using both OpenSSL and CryptoDev you may want to give the following patch a try:
- openssl-0.9.8k-cryptodev.diff
- Apply this patch to the original openssl-0.9.8k.tar.gz and recompile.
- openssl-0.9.7d-cryptodev.diff
- Apply this patch to the original openssl-0.9.7d.tar.gz and recompile.
Using CryptoAPI in the Linux kernel
(This is a bit off-topic here :-)
CryptoAPI is a Linux in-kernel infrastructure that offers cryptography to
all other subsystems. It provides a single API for accessing different
ciphers and digests.
- Simple Linux kernel module that shows how the encryption and decryption can be used:
- Read cryptoapi-demo.c below · Download raw cryptoapi-demo.c
Useful CryptoAPI-related links:
- CryptoAPI mailing list
- Archive and subscription page
- VIA PadLock support for Linux
- Patches for Kernel and OpenSSL
cryptodev-demo1.c source
/*
* Demo on how to use OpenBSD /dev/crypto device.
*
* Author: Michal Ludvig <michal@logix.cz>
* /michal
*
* Note: by default OpenBSD doesn't allow using
* /dev/crypto if there is no hardware accelerator
* for a given algorithm. To change this you'll have to
* set cryptodevallowsoft=1 in
* /usr/src/sys/crypto/cryptodev.c and rebuild your kernel.
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <crypto/cryptodev.h>
#define DATA_SIZE 4096
#define BLOCK_SIZE 16
#define KEY_SIZE 24
static int
test_crypto(int cfd)
{
struct {
char in[DATA_SIZE],
encrypted[DATA_SIZE],
decrypted[DATA_SIZE],
iv[BLOCK_SIZE],
key[KEY_SIZE];
} data;
struct session_op sess;
struct crypt_op cryp;
memset(&sess, 0, sizeof(sess));
memset(&cryp, 0, sizeof(cryp));
/* Use the garbage that is on the stack :-) */
/* memset(&data, 0, sizeof(data)); */
/* Get crypto session for AES128 */
sess.cipher = CRYPTO_AES_CBC;
sess.keylen = KEY_SIZE;
sess.key = data.key;
if (ioctl(cfd, CIOCGSESSION, &sess)) {
perror("ioctl(CIOCGSESSION)");
return 1;
}
/* Encrypt data.in to data.encrypted */
cryp.ses = sess.ses;
cryp.len = sizeof(data.in);
cryp.src = data.in;
cryp.dst = data.encrypted;
cryp.iv = data.iv;
cryp.op = COP_ENCRYPT;
if (ioctl(cfd, CIOCCRYPT, &cryp)) {
perror("ioctl(CIOCCRYPT)");
return 1;
}
/* Decrypt data.encrypted to data.decrypted */
cryp.src = data.encrypted;
cryp.dst = data.decrypted;
cryp.op = COP_DECRYPT;
if (ioctl(cfd, CIOCCRYPT, &cryp)) {
perror("ioctl(CIOCCRYPT)");
return 1;
}
/* Verify the result */
if (memcmp(data.in, data.decrypted, sizeof(data.in)) != 0) {
fprintf(stderr,
"FAIL: Decrypted data are different from the input data.\n");
return 1;
} else
printf("Test passed\n");
/* Finish crypto session */
if (ioctl(cfd, CIOCFSESSION, &sess.ses)) {
perror("ioctl(CIOCFSESSION)");
return 1;
}
return 0;
}
int
main()
{
int fd = -1, cfd = -1;
/* Open the crypto device */
fd = open("/dev/crypto", O_RDWR, 0);
if (fd < 0) {
perror("open(/dev/crypto)");
return 1;
}
/* Clone file descriptor */
if (ioctl(fd, CRIOGET, &cfd)) {
perror("ioctl(CRIOGET)");
return 1;
}
/* Set close-on-exec (not really neede here */
if (fcntl(cfd, F_SETFD, 1) == -1) {
perror("fcntl(F_SETFD)");
return 1;
}
/* Run the test itself */
if (test_crypto(cfd))
return 1;
/* Close cloned descriptor */
if (close(cfd)) {
perror("close(cfd)");
return 1;
}
/* Close the original descriptor */
if (close(fd)) {
perror("close(fd)");
return 1;
}
return 0;
}
cryptodev-demo2.c source
/*
* This demo combines CryptoDev and OpenSSL encryption
* to verify eachother.
*
* Author: Michal Ludvig <michal@logix.cz>
* /michal
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/cryptodev.h>
#include <openssl/evp.h>
#define DATA_SIZE 14096
#define BLOCK_SIZE 16
#define KEY_SIZE 16
#define ALG_NAME "aes"
#define MODE CRYPTO_FLAG_CBC
void
dump_mem(const char *msg, const unsigned char *buf, size_t len)
{
int i;
printf("%s\n", msg);
for (i = 0; i < len; i++)
printf("%02x%s", buf[i], (i%8==7) ? "\n" : "");
}
int
compare_buffers(const char *msg,
const char *buf1_label, const unsigned char *buf1,
const char *buf2_label, const unsigned char *buf2,
size_t len)
{
if (memcmp(buf1, buf2, len) != 0) {
size_t i, dump;
for (i = 0; i < len; i++)
if (buf1[i] != buf2[i])
break;
fprintf(stderr, "FAIL: %s differ at offset %zu\n", msg, i);
dump = len - i > 64 ? 64 : len - i;
dump_mem(buf1_label, &buf1[i], dump);
dump_mem(buf2_label, &buf2[i], dump);
return 1;
}
return 0;
}
static int
test_crypto(int cfd)
{
struct {
char in[DATA_SIZE],
encrypted[DATA_SIZE],
decrypted[DATA_SIZE],
iv[BLOCK_SIZE],
key[KEY_SIZE];
} data, data_openssl;
struct session_op sess;
struct crypt_op cryp;
char algbuf[100];
EVP_CIPHER_CTX ctx;
const EVP_CIPHER *c;
/* ==== Prepare the input data ==== */
/* Use the garbage that is on the stack :-) */
memset(&data, 0, sizeof(data));
memcpy (&data_openssl, &data, sizeof(data));
/* ==== First the CryptoDev pass ==== */
memset(&sess, 0, sizeof(sess));
memset(&cryp, 0, sizeof(cryp));
/* Get crypto session for AES128 */
sess.cipher = CRYPTO_CIPHER_NAME | MODE;
sess.alg_name = ALG_NAME;
sess.alg_namelen = strlen(sess.alg_name);
sess.keylen = KEY_SIZE;
sess.key = data.key;
if (ioctl(cfd, CIOCGSESSION, &sess)) {
perror("ioctl(CIOCGSESSION)");
return 1;
}
/* Encrypt data.in to data.encrypted */
cryp.ses = sess.ses;
cryp.len = sizeof(data.in);
cryp.src = data.in;
cryp.dst = data.encrypted;
cryp.iv = data.iv;
cryp.op = COP_ENCRYPT;
if (ioctl(cfd, CIOCCRYPT, &cryp)) {
perror("ioctl(CIOCCRYPT)");
return 1;
}
/* Decrypt data.encrypted to data.decrypted */
cryp.src = data.encrypted;
cryp.dst = data.decrypted;
cryp.op = COP_DECRYPT;
if (ioctl(cfd, CIOCCRYPT, &cryp)) {
perror("ioctl(CIOCCRYPT)");
return 1;
}
/* Finish crypto session */
if (ioctl(cfd, CIOCFSESSION, &sess.ses)) {
perror("ioctl(CIOCFSESSION)");
return 1;
}
/* ==== Now the OpenSSL pass ==== */
OpenSSL_add_all_algorithms();
EVP_CIPHER_CTX_init(&ctx);
snprintf(algbuf, sizeof(algbuf), "%s-%d-%s", ALG_NAME, KEY_SIZE * 8,
MODE & CRYPTO_FLAG_CBC ? "cbc" : "ecb");
c = EVP_get_cipherbyname(algbuf);
if (!c) {
perror("EVP_get_cipherbyname()");
return 1;
}
if (!EVP_CipherInit(&ctx, c, data_openssl.key, data_openssl.iv, 1)) {
perror("EVP_CipherInit()");
return 1;
}
if (!EVP_Cipher(&ctx, data_openssl.encrypted, data_openssl.in, sizeof(data_openssl.in))) {
perror("EVP_Cipher()");
return 1;
}
EVP_CIPHER_CTX_cleanup(&ctx);
EVP_CIPHER_CTX_init(&ctx);
if (!EVP_CipherInit(&ctx, c, data_openssl.key, data_openssl.iv, 0)) {
perror("EVP_CipherInit()");
return 1;
}
if (!EVP_Cipher(&ctx, data_openssl.decrypted, data_openssl.encrypted, sizeof(data_openssl.in))) {
perror("EVP_Cipher()");
return 1;
}
/* ==== Verify the results ==== */
if (compare_buffers("Encrypted data", "CryptoDev:", data.encrypted,
"OpenSSL:", data_openssl.encrypted, sizeof(data.encrypted)))
return 1;
if (compare_buffers("Decrypted data", "CryptoDev:", data.decrypted,
"OpenSSL:", data_openssl.decrypted, sizeof(data.decrypted)))
return 1;
printf("Test passed\n");
return 0;
}
int
main()
{
int fd = -1, cfd = -1;
/* Open the crypto device */
fd = open("/dev/crypto", O_RDWR, 0);
if (fd < 0) {
perror("open(/dev/crypto)");
return 1;
}
/* Clone file descriptor */
if (ioctl(fd, CRIOGET, &cfd)) {
perror("ioctl(CRIOGET)");
return 1;
}
/* Set close-on-exec (not really neede here) */
if (fcntl(cfd, F_SETFD, 1) == -1) {
perror("fcntl(F_SETFD)");
return 1;
}
/* Run the test itself */
if (test_crypto(cfd))
return 1;
/* Close cloned descriptor */
if (close(cfd)) {
perror("close(cfd)");
return 1;
}
/* Close the original descriptor */
if (close(fd)) {
perror("close(fd)");
return 1;
}
return 0;
}
cryptoapi-demo.c source
/*
* Simple demo explaining usage of the Linux kernel CryptoAPI.
* By Michal Ludvig <michal@logix.cz>
* /
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/crypto.h>
#include <linux/mm.h>
#include <asm/scatterlist.h>
#define PFX "cryptoapi-demo: "
MODULE_AUTHOR("Michal Ludvig <michal@logix.cz>");
MODULE_DESCRIPTION("Simple CryptoAPI demo");
MODULE_LICENSE("GPL");
/* ====== CryptoAPI ====== */
#define DATA_SIZE 16
#define FILL_SG(sg,ptr,len) do { (sg)->page = virt_to_page(ptr); (sg)->offset = offset_in_page(ptr); (sg)->length = len; } while (0)
static void
hexdump(unsigned char *buf, unsigned int len)
{
while (len--)
printk("%02x", *buf++);
printk("\n");
}
static void
cryptoapi_demo(void)
{
/* config options */
char *algo = "aes";
int mode = CRYPTO_TFM_MODE_CBC;
char key[16], iv[16];
/* local variables */
struct crypto_tfm *tfm;
struct scatterlist sg[8];
int ret;
char *input, *encrypted, *decrypted;
memset(key, 0, sizeof(key));
memset(iv, 0, sizeof(iv));
tfm = crypto_alloc_tfm (algo, mode);
if (tfm == NULL) {
printk("failed to load transform for %s %s\n", algo, mode == CRYPTO_TFM_MODE_CBC ? "CBC" : "");
return;
}
ret = crypto_cipher_setkey(tfm, key, sizeof(key));
if (ret) {
printk(KERN_ERR PFX "setkey() failed flags=%x\n", tfm->crt_flags);
goto out;
}
input = kmalloc(GFP_KERNEL, DATA_SIZE);
if (!input) {
printk(KERN_ERR PFX "kmalloc(input) failed\n");
goto out;
}
encrypted = kmalloc(GFP_KERNEL, DATA_SIZE);
if (!encrypted) {
printk(KERN_ERR PFX "kmalloc(encrypted) failed\n");
kfree(input);
goto out;
}
decrypted = kmalloc(GFP_KERNEL, DATA_SIZE);
if (!decrypted) {
printk(KERN_ERR PFX "kmalloc(decrypted) failed\n");
kfree(encrypted);
kfree(input);
goto out;
}
memset(input, 0, DATA_SIZE);
FILL_SG(&sg[0], input, DATA_SIZE);
FILL_SG(&sg[1], encrypted, DATA_SIZE);
FILL_SG(&sg[2], decrypted, DATA_SIZE);
crypto_cipher_set_iv(tfm, iv, crypto_tfm_alg_ivsize (tfm));
ret = crypto_cipher_encrypt(tfm, &sg[1], &sg[0], DATA_SIZE);
if (ret) {
printk(KERN_ERR PFX "encryption failed, flags=0x%x\n", tfm->crt_flags);
goto out_kfree;
}
crypto_cipher_set_iv(tfm, iv, crypto_tfm_alg_ivsize (tfm));
ret = crypto_cipher_decrypt(tfm, &sg[2], &sg[1], DATA_SIZE);
if (ret) {
printk(KERN_ERR PFX "decryption failed, flags=0x%x\n", tfm->crt_flags);
goto out_kfree;
}
printk(KERN_ERR PFX "IN: "); hexdump(input, DATA_SIZE);
printk(KERN_ERR PFX "EN: "); hexdump(encrypted, DATA_SIZE);
printk(KERN_ERR PFX "DE: "); hexdump(decrypted, DATA_SIZE);
if (memcmp(input, decrypted, DATA_SIZE) != 0)
printk(KERN_ERR PFX "FAIL: input buffer != decrypted buffer\n");
else
printk(KERN_ERR PFX "PASS: encryption/decryption verified\n");
out_kfree:
kfree(decrypted);
kfree(encrypted);
kfree(input);
out:
crypto_free_tfm(tfm);
}
/* ====== Module init/exit ====== */
static int __init
init_cryptoapi_demo(void)
{
cryptoapi_demo();
return 0;
}
static void __exit
exit_cryptoapi_demo(void)
{
}
module_init(init_cryptoapi_demo);
module_exit(exit_cryptoapi_demo);