Hey! I'm David, cofounder of zkSecurity and the author of the Real-World Cryptography book. I was previously a crypto architect at O(1) Labs (working on the Mina cryptocurrency), before that I was the security lead for Diem (formerly Libra) at Novi (Facebook), and a security consultant for the Cryptography Services of NCC Group. This is my blog about cryptography and security and other related topics that I find interesting.

I'll be back in Vegas this year to give the crypto training of Black Hat. The class is not full yet so hurry up if that is something that interests you.

It will be a blend of culture, exercises and technical dives. For 2 days, students get to learn all the cool crypto attacks, get to dive into some of them deeply, and get to interact via numerous exercises.

Noise is a protocol framework allowing you to build different lightweight TLS-like handshakes depending on your use case. Benefits are a short code size, very few dependencies, simplicity of the security guarantees and analysis. It focuses primarily on the initial asymmetric phase of the setup of a secure channel, but does leave you with two ciphers that you can use to read and write on both sides of the connection. If you want to know more, I wrote a readable implementation, and have a tutorial video.

Strobe is a protocol framework as well, focusing on the symmetric part of the protocol. Its simplicity boils down to only using one cryptographic primitive: the duplex construction. Which allows developers to benefit from an ultra short cryptographic code base supporting their custom-made symmetric protocols as well as their different needs of cryptographic functions. Indeed, Strobe can be used as well to instantiate a hash function, a key derivation function, a pseudo-random number generator, a message authentication code, an authenticated encryption with associated data cipher, etc... If you want to know more, I wrote a readable implementation and Mike Hamburg gave a talk at RWC.

Noise+Strobe=Disco. One of Noise's major character is that it keeps a running hash, digesting every message and allowing every new handshake message to mix the transcript in its encryption while authenticating previous messages received and sent. Strobe works like that naturally. Its duplex function absorbs every calls being made to the underlying primitive (the Keccak permutation), to the extent that every new operation is influenced by any operation that happened previously. These two common traits in Strobe and Noise led me to pursue a merge between the two: what if that running hash and symmetric state in Noise was simply Strobe's primitive? And what if at the end of a handshake Noise would just spew out two Strobe's objects also depending on the handshake transcript? I talked to Trevor Perrin about it and his elegant suggestion for a name (Disco) and my curiosity led to an implementation of what it would look like.

This is of course highly experimental. I modified the Noise's specification to see how much I could remove/simplify from it and the result is already enjoyable.

I've discussed the changes on the mailing list. But simply put: the CipherState has been removed, the SymmetricState has been replaced by calls to Strobe. This leaves us only with one object: the HandshakeState. Every symmetric algorithm has been removed (HDKF, HMAC, HASH, AEAD). The specification looks way shorter, while the Disco implementation is more than half the size of the Noise implementation.

The Strobe's calls naturally absorbs every operation, and can encrypt/decrypt the handshake messages even if no shared secret has been negotiated (with a non-keyed duplex construction), which simplifies corner cases where you would have to test if you have already negotiated a shared secret or not.

I wrote an implementation of the Noise Protocol Framework. If you don't know what that is, it is a framework to create lightweight TLS-like protocols. If you do not want to use TLS because it is unnecessarily complicated, and you know what you're doing, Noise is the solution. You have different patterns for different usecase and everything is well explained for you to implement it smoothly.

To learn more about Noise you can also check this screencast I shot last year:

One awesome feature of Go is cross-compilation. One limitation is that we can only choose to build for some pre-defined architectures and OS, but we can't build per CPU-model. In the previous post I was talking about C programs, where the user actually chooses the CPU model when calling the Make. Go could probably have something like that but it wouldn't be gooy. One solution is to build for every CPU models anyway, and decide later what is good to be used. So one assembly code for SSE2, one code for AVX, one code for AVX-512.

Note that we do not need to use SSE3/SSE4 (or AVX2) as the interesting functions are contained in SSE2 (respectively AVX) which will have more support and be contained in greater versions of SSE (respectively AVX) anyway.

The official Blake2 implementation in Go actually uses SIMD instructions. Looking at it is a good way to see how SIMD coding works in Go.

In _amd64.go, they use the builtin init() function to figure out at runtime what is supported by the host architecture:

In the second solution, the runtime variables seems to be undocumented and only available since go1.7, they are probably filled via cpuid calls as well. Surprisingly, the internal/cpu package already has all the necessary functions to detect flavors of SIMD. See an example of use in the bytes package.

And that's it! Blake2's hashBlocks() function then dynamically decides which function to use at runtime:

func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) {
if useAVX2 {
hashBlocksAVX2(h, c, flag, blocks)
} else if useAVX {
hashBlocksAVX(h, c, flag, blocks)
} else if useSSE4 {
hashBlocksSSE4(h, c, flag, blocks)
} else {
hashBlocksGeneric(h, c, flag, blocks)
}
}

Because Go does not have intrisic functions for SIMD, these are implemented directly in assembly. You can look at the code in the relevant _amd64.s file. Now it's kind of tricky because Go has invented its own assembly language (based on Plan9) and you have to find out things the hard way. Instructions like VINSERTI128 and VPSHUFD are the SIMD instructions. MMX registers are M0...M7, SSE registers are X0...X15, AVX registers are Y0, ..., Y15. MOVDQA is called MOVO (or MOVOA) and MOVDQU is called MOVOU. Things like that.

As for AVX-512, Go probably still doesn't have instructions for that. So you'll need to write the raw opcodes yourself using BYTE (like here) and as explained here.

The Keccak Code Package repository contains all of the Keccak team's constructions, including for example SHA-3, SHAKE, cSHAKE, ParallelHash, TupleHash, KMAC, Keyak, Ketje and KangarooTwelve. ParallelHash and KangarooTwelve are two hash functions based on the same basis of SHA-3, but that can be sped up with parallelization. This makes these two hash functions really interesting, especially when hashing big files.

MMX, SSE, SSE2, AVX, AVX2, AVX-512

To support parallelization, a common way is to use SIMD instructions, a set of instructions generally available on any modern 64-bit architecture that allows computation on large blocks of data (64, 128, 256 or 512 bits). Using them to operate in blocks of data is what we often call vector/array programming, the compiler will sometimes optimize your code by automatically using these large SIMD registers.

SIMD instructions have been here since the 70s, and have become really common. This is one of the reason why image, sound, video and games all work so well nowadays. Generally, if you're on a 64-bit architecture your CPU will support SIMD instructions.

There are several versions of these instructions. On Intel's side these are called MMX, SSE and AVX instructions. AMD has SSE and AVX instructions as well. On ARM these are called NEON instructions.

MMX allows you to operate on 64-bit registers at once (called MM registers). SSE, SSE2, SSE3 and SSE4 all allow you to use 128-bit registers (XMM registers). AVX and AVX2 introduced 256-bit registers (YMM registers) and the more recent AVX-512 supports 512-bit registers (ZMM registers).

Looking at the list of architectures supported by the Keccak Code Package I see Haswell, which is of the same family and supports AVX2 as well. Compiling with it, I can run my KangarooTwelve code with AVX2 support, which parallelizes four runs of the Keccak permutation at the same time using these 256-bit registers!

In more details, the Keccak permutation goes through several rounds (12 for KangarooTwelve, 24 for ParallelHash) that need to serially operate on a succession of 64-bit lanes. AVX (no need for AVX2) 256-bit's registers allow four 64-bit lanes to be operated on at the same time. That's effectively four Keccak permutations running in parallel.

Intrisic Instructions

Intrisic functions are functions you can use directly in code, and that are later recognized and handled by the compiler.

In C, if you're compiling with GCC on an Intel/AMD architecture you can start using intrisic functions for SIMD by including x86intrin.h. Or you can use this script to include the correct file for different combination of compilers and architectures:

If we look at the reference implementation of KangarooTwelve in C we can see how they decided to use the AVX2 instructions. They first define a __m256i variable which will hold 4 lanes at the same time.

#define ANDnu256(a, b) _mm256_andnot_si256(a, b)
#define CONST256(a) _mm256_load_si256((const V256 *)&(a))
#define CONST256_64(a) (V256)_mm256_broadcast_sd((const double*)(&a))
#define LOAD256(a) _mm256_load_si256((const V256 *)&(a))
#define LOAD256u(a) _mm256_loadu_si256((const V256 *)&(a))
#define LOAD4_64(a, b, c, d) _mm256_set_epi64x((UINT64)(a), (UINT64)(b), (UINT64)(c), (UINT64)(d))
#define ROL64in256(d, a, o) d = _mm256_or_si256(_mm256_slli_epi64(a, o), _mm256_srli_epi64(a, 64-(o)))
#define ROL64in256_8(d, a) d = _mm256_shuffle_epi8(a, CONST256(rho8))
#define ROL64in256_56(d, a) d = _mm256_shuffle_epi8(a, CONST256(rho56))
#define STORE256(a, b) _mm256_store_si256((V256 *)&(a), b)
#define STORE256u(a, b) _mm256_storeu_si256((V256 *)&(a), b)
#define STORE2_128(ah, al, v) _mm256_storeu2_m128d((V128*)&(ah), (V128*)&(al), v)
#define XOR256(a, b) _mm256_xor_si256(a, b)
#define XOReq256(a, b) a = _mm256_xor_si256(a, b)
#define UNPACKL( a, b ) _mm256_unpacklo_epi64((a), (b))
#define UNPACKH( a, b ) _mm256_unpackhi_epi64((a), (b))
#define PERM128( a, b, c ) (V256)_mm256_permute2f128_ps((__m256)(a), (__m256)(b), c)
#define SHUFFLE64( a, b, c ) (V256)_mm256_shuffle_pd((__m256d)(a), (__m256d)(b), c)

And if you're wondering how each of these _mm256 function is used, you can check the same Intel documentation

a lot of keywords here are really interesting. But first, what is a Mersenne prime?

A mersenne prime is simply a prime \(p\) such that \(p=2^n - 1\). The nice thing about that, is that the programming way of writing such a number is

(1 << n) - 1

which is a long series of 1s.

A number modulo this prime can be any bitstring of the mersenne prime's length.

OK we know what's a Mersenne prime. How do we build our new public key cryptosystem now?

Let's start with a private key: (secret, privkey), two bitstrings of low hamming weight. Meaning that they do not have a lot of bits set to 1.

Now something very intuitive happens: the inverse of such a bitstring will probably have a high hamming weight, which let us believe that \(secret \cdot privkey^{-1} \pmod{p}\) looks random. This will be our public key.

Now that we have a private key and a public key. How do we encrypt ?

The paper starts with a very simple scheme on how to encrypt a bit \(b\).

\[ciphertext = (-1)^b \cdot ( A \cdot pubkey + B ) \pmod{p} \]

with \(A\) and \(B\) two public numbers that have low hamming weights as well.

We can see intuitively that the ciphertext will have a high hamming weight (and thus might look random).

If you are not convinced, all of this is based on actual proofs that such operations between low and high hamming weight bitstrings will yield other low or high hamming weight bitstrings. All of this really work because we are modulo a \(1111\cdots\) kind of number. The following lemmas taken from the paper are proven in section 2.1.

How do you decrypt such an encrypted bit?

This is how:

\[ciphertext \cdot privkey \pmod{p}\]

This will yield either a low hamming weight number → the original bit \(b\) was a \(0\),
or a high hamming weight number → the original bit \(b\) was a \(1\).

You can convince yourself by following the equation:

And again, intuitively you can see that everything is low hamming weight except for the value of \((-1)^b\).

This scheme doesn't look CCA secure nor practical. The paper goes on with an explanation of a more involved cryptosystem in section 6.

EDIT: there is already a reduction of the security estimates published on eprint.

Recently T. Duong, D. Bleichenbacher, Q. Nguyen and B. Przydatek released a crypto library intitled Tink. At its center lied an implementation of AES-GCM somehow different from the rest: it did not take a nonce as one of its input.

A few days ago, at the Crypto SummerSchool in Croatia, Nik Kinkel told me that he would generally recommend against letting developers tweak the nonce value, based on how AES-GCM tended to be heavily mis-used in the wild. For a recap, if a nonce is used twice to encrypt two different messages AES-GCM will leak the authentication key.

I think it's a fair improvement of AES-GCM to remove the nonce argument. By doing so, nonces have to be randomly generated. Now the new danger is that the same nonce is randomly generated twice for the same key. The birthday bound tells us that after \(2^{n/2}\) messages, \(n\) being the bit-size of a nonce, you have great odds of generating a previous nonce.

The optimal rekey point has been studied by Abdalla and Bellare and can computed with the cubic root of the nonce space. If more nonces are generated after that, chances of a nonce collision are too high. For AES-GCM this means that after \(2^{92/3} = 1704458900\) different messages, the key should be rotated.

This is of course assuming that you use 92-bit nonces with 32-bit counters. Some protocol and implementations will actually fix the first 32 bits of these 92-bit nonces reducing the birthday bound even further.

Isn't that a bit low?

Yes it kinda is. An interesting construction by Dan J. Bernstein called XSalsa20 (and that can be extended to XChacha20) allow us to use nonces of 192 bits. This would mean that you should be able to use the same key for up to \(2^{192/3} = 18446744073709551616\) messages. Which is already twice more that what a BIG INT can store in a database

It seems like Sponge-based AEADs should benefit from large nonces as well since their rate can store even more bits. This might be a turning point for these constructions in the last round of the CAESAR competition. There are currently 4 of these: Ascon, Ketje, Keyak and NORX.

With that in mind, is nonce mis-use resistance now fixed?

EDIT: Here is a list of recent papers on the subject: