2018-11-17 04:26:29 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* NHPoly1305 - ε - almost - ∆ - universal hash function for Adiantum
*
* Copyright 2018 Google LLC
*/
/*
* " NHPoly1305 " is the main component of Adiantum hashing .
* Specifically , it is the calculation
*
2018-12-07 01:21:59 +03:00
* H_L ← Poly1305_ { K_L } ( NH_ { K_N } ( pad_ { 128 } ( L ) ) )
2018-11-17 04:26:29 +03:00
*
2018-12-07 01:21:59 +03:00
* from the procedure in section 6.4 of the Adiantum paper [ 1 ] . It is an
* ε - almost - ∆ - universal ( ε - ∆ U ) hash function for equal - length inputs over
2018-11-17 04:26:29 +03:00
* Z / ( 2 ^ { 128 } Z ) , where the " ∆ " operation is addition . It hashes 1024 - byte
* chunks of the input with the NH hash function [ 2 ] , reducing the input length
* by 32 x . The resulting NH digests are evaluated as a polynomial in
* GF ( 2 ^ { 130 } - 5 ) , like in the Poly1305 MAC [ 3 ] . Note that the polynomial
2018-12-07 01:21:59 +03:00
* evaluation by itself would suffice to achieve the ε - ∆ U property ; NH is used
2018-11-17 04:26:29 +03:00
* for performance since it ' s over twice as fast as Poly1305 .
*
* This is * not * a cryptographic hash function ; do not use it as such !
*
* [ 1 ] Adiantum : length - preserving encryption for entry - level processors
* ( https : //eprint.iacr.org/2018/720.pdf)
* [ 2 ] UMAC : Fast and Secure Message Authentication
* ( https : //fastcrypto.org/umac/umac_proc.pdf)
* [ 3 ] The Poly1305 - AES message - authentication code
* ( https : //cr.yp.to/mac/poly1305-20050329.pdf)
*/
# include <asm/unaligned.h>
# include <crypto/algapi.h>
# include <crypto/internal/hash.h>
# include <crypto/nhpoly1305.h>
# include <linux/crypto.h>
# include <linux/kernel.h>
# include <linux/module.h>
static void nh_generic ( const u32 * key , const u8 * message , size_t message_len ,
__le64 hash [ NH_NUM_PASSES ] )
{
u64 sums [ 4 ] = { 0 , 0 , 0 , 0 } ;
BUILD_BUG_ON ( NH_PAIR_STRIDE ! = 2 ) ;
BUILD_BUG_ON ( NH_NUM_PASSES ! = 4 ) ;
while ( message_len ) {
u32 m0 = get_unaligned_le32 ( message + 0 ) ;
u32 m1 = get_unaligned_le32 ( message + 4 ) ;
u32 m2 = get_unaligned_le32 ( message + 8 ) ;
u32 m3 = get_unaligned_le32 ( message + 12 ) ;
sums [ 0 ] + = ( u64 ) ( u32 ) ( m0 + key [ 0 ] ) * ( u32 ) ( m2 + key [ 2 ] ) ;
sums [ 1 ] + = ( u64 ) ( u32 ) ( m0 + key [ 4 ] ) * ( u32 ) ( m2 + key [ 6 ] ) ;
sums [ 2 ] + = ( u64 ) ( u32 ) ( m0 + key [ 8 ] ) * ( u32 ) ( m2 + key [ 10 ] ) ;
sums [ 3 ] + = ( u64 ) ( u32 ) ( m0 + key [ 12 ] ) * ( u32 ) ( m2 + key [ 14 ] ) ;
sums [ 0 ] + = ( u64 ) ( u32 ) ( m1 + key [ 1 ] ) * ( u32 ) ( m3 + key [ 3 ] ) ;
sums [ 1 ] + = ( u64 ) ( u32 ) ( m1 + key [ 5 ] ) * ( u32 ) ( m3 + key [ 7 ] ) ;
sums [ 2 ] + = ( u64 ) ( u32 ) ( m1 + key [ 9 ] ) * ( u32 ) ( m3 + key [ 11 ] ) ;
sums [ 3 ] + = ( u64 ) ( u32 ) ( m1 + key [ 13 ] ) * ( u32 ) ( m3 + key [ 15 ] ) ;
key + = NH_MESSAGE_UNIT / sizeof ( key [ 0 ] ) ;
message + = NH_MESSAGE_UNIT ;
message_len - = NH_MESSAGE_UNIT ;
}
hash [ 0 ] = cpu_to_le64 ( sums [ 0 ] ) ;
hash [ 1 ] = cpu_to_le64 ( sums [ 1 ] ) ;
hash [ 2 ] = cpu_to_le64 ( sums [ 2 ] ) ;
hash [ 3 ] = cpu_to_le64 ( sums [ 3 ] ) ;
}
/* Pass the next NH hash value through Poly1305 */
static void process_nh_hash_value ( struct nhpoly1305_state * state ,
const struct nhpoly1305_key * key )
{
BUILD_BUG_ON ( NH_HASH_BYTES % POLY1305_BLOCK_SIZE ! = 0 ) ;
poly1305_core_blocks ( & state - > poly_state , & key - > poly_key , state - > nh_hash ,
NH_HASH_BYTES / POLY1305_BLOCK_SIZE ) ;
}
/*
* Feed the next portion of the source data , as a whole number of 16 - byte
* " NH message units " , through NH and Poly1305 . Each NH hash is taken over
* 1024 bytes , except possibly the final one which is taken over a multiple of
* 16 bytes up to 1024. Also , in the case where data is passed in misaligned
* chunks , we combine partial hashes ; the end result is the same either way .
*/
static void nhpoly1305_units ( struct nhpoly1305_state * state ,
const struct nhpoly1305_key * key ,
const u8 * src , unsigned int srclen , nh_t nh_fn )
{
do {
unsigned int bytes ;
if ( state - > nh_remaining = = 0 ) {
/* Starting a new NH message */
bytes = min_t ( unsigned int , srclen , NH_MESSAGE_BYTES ) ;
nh_fn ( key - > nh_key , src , bytes , state - > nh_hash ) ;
state - > nh_remaining = NH_MESSAGE_BYTES - bytes ;
} else {
/* Continuing a previous NH message */
__le64 tmp_hash [ NH_NUM_PASSES ] ;
unsigned int pos ;
int i ;
pos = NH_MESSAGE_BYTES - state - > nh_remaining ;
bytes = min ( srclen , state - > nh_remaining ) ;
nh_fn ( & key - > nh_key [ pos / 4 ] , src , bytes , tmp_hash ) ;
for ( i = 0 ; i < NH_NUM_PASSES ; i + + )
le64_add_cpu ( & state - > nh_hash [ i ] ,
le64_to_cpu ( tmp_hash [ i ] ) ) ;
state - > nh_remaining - = bytes ;
}
if ( state - > nh_remaining = = 0 )
process_nh_hash_value ( state , key ) ;
src + = bytes ;
srclen - = bytes ;
} while ( srclen ) ;
}
int crypto_nhpoly1305_setkey ( struct crypto_shash * tfm ,
const u8 * key , unsigned int keylen )
{
struct nhpoly1305_key * ctx = crypto_shash_ctx ( tfm ) ;
int i ;
if ( keylen ! = NHPOLY1305_KEY_SIZE )
return - EINVAL ;
poly1305_core_setkey ( & ctx - > poly_key , key ) ;
key + = POLY1305_BLOCK_SIZE ;
for ( i = 0 ; i < NH_KEY_WORDS ; i + + )
ctx - > nh_key [ i ] = get_unaligned_le32 ( key + i * sizeof ( u32 ) ) ;
return 0 ;
}
EXPORT_SYMBOL ( crypto_nhpoly1305_setkey ) ;
int crypto_nhpoly1305_init ( struct shash_desc * desc )
{
struct nhpoly1305_state * state = shash_desc_ctx ( desc ) ;
poly1305_core_init ( & state - > poly_state ) ;
state - > buflen = 0 ;
state - > nh_remaining = 0 ;
return 0 ;
}
EXPORT_SYMBOL ( crypto_nhpoly1305_init ) ;
int crypto_nhpoly1305_update_helper ( struct shash_desc * desc ,
const u8 * src , unsigned int srclen ,
nh_t nh_fn )
{
struct nhpoly1305_state * state = shash_desc_ctx ( desc ) ;
const struct nhpoly1305_key * key = crypto_shash_ctx ( desc - > tfm ) ;
unsigned int bytes ;
if ( state - > buflen ) {
bytes = min ( srclen , ( int ) NH_MESSAGE_UNIT - state - > buflen ) ;
memcpy ( & state - > buffer [ state - > buflen ] , src , bytes ) ;
state - > buflen + = bytes ;
if ( state - > buflen < NH_MESSAGE_UNIT )
return 0 ;
nhpoly1305_units ( state , key , state - > buffer , NH_MESSAGE_UNIT ,
nh_fn ) ;
state - > buflen = 0 ;
src + = bytes ;
srclen - = bytes ;
}
if ( srclen > = NH_MESSAGE_UNIT ) {
bytes = round_down ( srclen , NH_MESSAGE_UNIT ) ;
nhpoly1305_units ( state , key , src , bytes , nh_fn ) ;
src + = bytes ;
srclen - = bytes ;
}
if ( srclen ) {
memcpy ( state - > buffer , src , srclen ) ;
state - > buflen = srclen ;
}
return 0 ;
}
EXPORT_SYMBOL ( crypto_nhpoly1305_update_helper ) ;
int crypto_nhpoly1305_update ( struct shash_desc * desc ,
const u8 * src , unsigned int srclen )
{
return crypto_nhpoly1305_update_helper ( desc , src , srclen , nh_generic ) ;
}
EXPORT_SYMBOL ( crypto_nhpoly1305_update ) ;
int crypto_nhpoly1305_final_helper ( struct shash_desc * desc , u8 * dst , nh_t nh_fn )
{
struct nhpoly1305_state * state = shash_desc_ctx ( desc ) ;
const struct nhpoly1305_key * key = crypto_shash_ctx ( desc - > tfm ) ;
if ( state - > buflen ) {
memset ( & state - > buffer [ state - > buflen ] , 0 ,
NH_MESSAGE_UNIT - state - > buflen ) ;
nhpoly1305_units ( state , key , state - > buffer , NH_MESSAGE_UNIT ,
nh_fn ) ;
}
if ( state - > nh_remaining )
process_nh_hash_value ( state , key ) ;
poly1305_core_emit ( & state - > poly_state , dst ) ;
return 0 ;
}
EXPORT_SYMBOL ( crypto_nhpoly1305_final_helper ) ;
int crypto_nhpoly1305_final ( struct shash_desc * desc , u8 * dst )
{
return crypto_nhpoly1305_final_helper ( desc , dst , nh_generic ) ;
}
EXPORT_SYMBOL ( crypto_nhpoly1305_final ) ;
static struct shash_alg nhpoly1305_alg = {
. base . cra_name = " nhpoly1305 " ,
. base . cra_driver_name = " nhpoly1305-generic " ,
. base . cra_priority = 100 ,
. base . cra_ctxsize = sizeof ( struct nhpoly1305_key ) ,
. base . cra_module = THIS_MODULE ,
. digestsize = POLY1305_DIGEST_SIZE ,
. init = crypto_nhpoly1305_init ,
. update = crypto_nhpoly1305_update ,
. final = crypto_nhpoly1305_final ,
. setkey = crypto_nhpoly1305_setkey ,
. descsize = sizeof ( struct nhpoly1305_state ) ,
} ;
static int __init nhpoly1305_mod_init ( void )
{
return crypto_register_shash ( & nhpoly1305_alg ) ;
}
static void __exit nhpoly1305_mod_exit ( void )
{
crypto_unregister_shash ( & nhpoly1305_alg ) ;
}
module_init ( nhpoly1305_mod_init ) ;
module_exit ( nhpoly1305_mod_exit ) ;
MODULE_DESCRIPTION ( " NHPoly1305 ε-almost-∆-universal hash function " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Eric Biggers <ebiggers@google.com> " ) ;
MODULE_ALIAS_CRYPTO ( " nhpoly1305 " ) ;
MODULE_ALIAS_CRYPTO ( " nhpoly1305-generic " ) ;