diff options
Diffstat (limited to 'crypto/cfb.c')
-rw-r--r-- | crypto/cfb.c | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/crypto/cfb.c b/crypto/cfb.c new file mode 100644 index 000000000..5c36b7b65 --- /dev/null +++ b/crypto/cfb.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CFB: Cipher FeedBack mode + * + * Copyright (c) 2018 James.Bottomley@HansenPartnership.com + * + * CFB is a stream cipher mode which is layered on to a block + * encryption scheme. It works very much like a one time pad where + * the pad is generated initially from the encrypted IV and then + * subsequently from the encrypted previous block of ciphertext. The + * pad is XOR'd into the plain text to get the final ciphertext. + * + * The scheme of CFB is best described by wikipedia: + * + * https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CFB + * + * Note that since the pad for both encryption and decryption is + * generated by an encryption operation, CFB never uses the block + * decryption function. + */ + +#include <crypto/algapi.h> +#include <crypto/internal/cipher.h> +#include <crypto/internal/skcipher.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> + +static unsigned int crypto_cfb_bsize(struct crypto_skcipher *tfm) +{ + return crypto_cipher_blocksize(skcipher_cipher_simple(tfm)); +} + +static void crypto_cfb_encrypt_one(struct crypto_skcipher *tfm, + const u8 *src, u8 *dst) +{ + crypto_cipher_encrypt_one(skcipher_cipher_simple(tfm), dst, src); +} + +/* final encrypt and decrypt is the same */ +static void crypto_cfb_final(struct skcipher_walk *walk, + struct crypto_skcipher *tfm) +{ + const unsigned long alignmask = crypto_skcipher_alignmask(tfm); + u8 tmp[MAX_CIPHER_BLOCKSIZE + MAX_CIPHER_ALIGNMASK]; + u8 *stream = PTR_ALIGN(tmp + 0, alignmask + 1); + u8 *src = walk->src.virt.addr; + u8 *dst = walk->dst.virt.addr; + u8 *iv = walk->iv; + unsigned int nbytes = walk->nbytes; + + crypto_cfb_encrypt_one(tfm, iv, stream); + crypto_xor_cpy(dst, stream, src, nbytes); +} + +static int crypto_cfb_encrypt_segment(struct skcipher_walk *walk, + struct crypto_skcipher *tfm) +{ + const unsigned int bsize = crypto_cfb_bsize(tfm); + unsigned int nbytes = walk->nbytes; + u8 *src = walk->src.virt.addr; + u8 *dst = walk->dst.virt.addr; + u8 *iv = walk->iv; + + do { + crypto_cfb_encrypt_one(tfm, iv, dst); + crypto_xor(dst, src, bsize); + iv = dst; + + src += bsize; + dst += bsize; + } while ((nbytes -= bsize) >= bsize); + + memcpy(walk->iv, iv, bsize); + + return nbytes; +} + +static int crypto_cfb_encrypt_inplace(struct skcipher_walk *walk, + struct crypto_skcipher *tfm) +{ + const unsigned int bsize = crypto_cfb_bsize(tfm); + unsigned int nbytes = walk->nbytes; + u8 *src = walk->src.virt.addr; + u8 *iv = walk->iv; + u8 tmp[MAX_CIPHER_BLOCKSIZE]; + + do { + crypto_cfb_encrypt_one(tfm, iv, tmp); + crypto_xor(src, tmp, bsize); + iv = src; + + src += bsize; + } while ((nbytes -= bsize) >= bsize); + + memcpy(walk->iv, iv, bsize); + + return nbytes; +} + +static int crypto_cfb_encrypt(struct skcipher_request *req) +{ + struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); + struct skcipher_walk walk; + unsigned int bsize = crypto_cfb_bsize(tfm); + int err; + + err = skcipher_walk_virt(&walk, req, false); + + while (walk.nbytes >= bsize) { + if (walk.src.virt.addr == walk.dst.virt.addr) + err = crypto_cfb_encrypt_inplace(&walk, tfm); + else + err = crypto_cfb_encrypt_segment(&walk, tfm); + err = skcipher_walk_done(&walk, err); + } + + if (walk.nbytes) { + crypto_cfb_final(&walk, tfm); + err = skcipher_walk_done(&walk, 0); + } + + return err; +} + +static int crypto_cfb_decrypt_segment(struct skcipher_walk *walk, + struct crypto_skcipher *tfm) +{ + const unsigned int bsize = crypto_cfb_bsize(tfm); + unsigned int nbytes = walk->nbytes; + u8 *src = walk->src.virt.addr; + u8 *dst = walk->dst.virt.addr; + u8 *iv = walk->iv; + + do { + crypto_cfb_encrypt_one(tfm, iv, dst); + crypto_xor(dst, src, bsize); + iv = src; + + src += bsize; + dst += bsize; + } while ((nbytes -= bsize) >= bsize); + + memcpy(walk->iv, iv, bsize); + + return nbytes; +} + +static int crypto_cfb_decrypt_inplace(struct skcipher_walk *walk, + struct crypto_skcipher *tfm) +{ + const unsigned int bsize = crypto_cfb_bsize(tfm); + unsigned int nbytes = walk->nbytes; + u8 *src = walk->src.virt.addr; + u8 * const iv = walk->iv; + u8 tmp[MAX_CIPHER_BLOCKSIZE]; + + do { + crypto_cfb_encrypt_one(tfm, iv, tmp); + memcpy(iv, src, bsize); + crypto_xor(src, tmp, bsize); + src += bsize; + } while ((nbytes -= bsize) >= bsize); + + return nbytes; +} + +static int crypto_cfb_decrypt_blocks(struct skcipher_walk *walk, + struct crypto_skcipher *tfm) +{ + if (walk->src.virt.addr == walk->dst.virt.addr) + return crypto_cfb_decrypt_inplace(walk, tfm); + else + return crypto_cfb_decrypt_segment(walk, tfm); +} + +static int crypto_cfb_decrypt(struct skcipher_request *req) +{ + struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); + struct skcipher_walk walk; + const unsigned int bsize = crypto_cfb_bsize(tfm); + int err; + + err = skcipher_walk_virt(&walk, req, false); + + while (walk.nbytes >= bsize) { + err = crypto_cfb_decrypt_blocks(&walk, tfm); + err = skcipher_walk_done(&walk, err); + } + + if (walk.nbytes) { + crypto_cfb_final(&walk, tfm); + err = skcipher_walk_done(&walk, 0); + } + + return err; +} + +static int crypto_cfb_create(struct crypto_template *tmpl, struct rtattr **tb) +{ + struct skcipher_instance *inst; + struct crypto_alg *alg; + int err; + + inst = skcipher_alloc_instance_simple(tmpl, tb); + if (IS_ERR(inst)) + return PTR_ERR(inst); + + alg = skcipher_ialg_simple(inst); + + /* CFB mode is a stream cipher. */ + inst->alg.base.cra_blocksize = 1; + + /* + * To simplify the implementation, configure the skcipher walk to only + * give a partial block at the very end, never earlier. + */ + inst->alg.chunksize = alg->cra_blocksize; + + inst->alg.encrypt = crypto_cfb_encrypt; + inst->alg.decrypt = crypto_cfb_decrypt; + + err = skcipher_register_instance(tmpl, inst); + if (err) + inst->free(inst); + + return err; +} + +static struct crypto_template crypto_cfb_tmpl = { + .name = "cfb", + .create = crypto_cfb_create, + .module = THIS_MODULE, +}; + +static int __init crypto_cfb_module_init(void) +{ + return crypto_register_template(&crypto_cfb_tmpl); +} + +static void __exit crypto_cfb_module_exit(void) +{ + crypto_unregister_template(&crypto_cfb_tmpl); +} + +subsys_initcall(crypto_cfb_module_init); +module_exit(crypto_cfb_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CFB block cipher mode of operation"); +MODULE_ALIAS_CRYPTO("cfb"); +MODULE_IMPORT_NS(CRYPTO_INTERNAL); |