File CVE-2023-52323-const_time-decoding.patch of Package python-pycryptodomex.32629
From 0deea1bfe1489e8c80d2053bbb06a1aa0b181ebd Mon Sep 17 00:00:00 2001
From: Helder Eijs <helderijs@gmail.com>
Date: Wed, 27 Dec 2023 09:39:22 +0100
Subject: [PATCH] Use constant-time (faster) padding decoding also for OAEP
---
lib/Crypto/Cipher/PKCS1_OAEP.py | 34 +-
lib/Crypto/Cipher/PKCS1_v1_5.py | 28 +-
lib/Crypto/Cipher/_pkcs1_oaep_decode.py | 41 +++
lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py | 6
lib/Crypto/Util/py3compat.py | 10
setup.py | 4
src/pkcs1_decode.c | 354 ++++++++++++++++++++++++++++
src/test/Makefile | 5
8 files changed, 449 insertions(+), 33 deletions(-)
create mode 100644 lib/Crypto/Cipher/_pkcs1_oaep_decode.py
--- a/lib/Crypto/Cipher/PKCS1_OAEP.py
+++ b/lib/Crypto/Cipher/PKCS1_OAEP.py
@@ -23,11 +23,13 @@
from Crypto.Signature.pss import MGF1
import Crypto.Hash.SHA1
-from Crypto.Util.py3compat import bord, _copy_bytes
+from Crypto.Util.py3compat import _copy_bytes
import Crypto.Util.number
-from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes
-from Crypto.Util.strxor import strxor
+from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes
+from Crypto.Util.strxor import strxor
from Crypto import Random
+from ._pkcs1_oaep_decode import oaep_decode
+
class PKCS1OAEP_Cipher:
"""Cipher object for PKCS#1 v1.5 OAEP.
@@ -68,7 +70,7 @@ class PKCS1OAEP_Cipher:
if mgfunc:
self._mgf = mgfunc
else:
- self._mgf = lambda x,y: MGF1(x,y,self._hashObj)
+ self._mgf = lambda x, y: MGF1(x, y, self._hashObj)
self._label = _copy_bytes(None, None, label)
self._randfunc = randfunc
@@ -105,7 +107,7 @@ class PKCS1OAEP_Cipher:
# See 7.1.1 in RFC3447
modBits = Crypto.Util.number.size(self._key.n)
- k = ceil_div(modBits, 8) # Convert from bits to bytes
+ k = ceil_div(modBits, 8) # Convert from bits to bytes
hLen = self._hashObj.digest_size
mLen = len(message)
@@ -159,11 +161,11 @@ class PKCS1OAEP_Cipher:
# See 7.1.2 in RFC3447
modBits = Crypto.Util.number.size(self._key.n)
- k = ceil_div(modBits,8) # Convert from bits to bytes
+ k = ceil_div(modBits, 8) # Convert from bits to bytes
hLen = self._hashObj.digest_size
# Step 1b and 1c
- if len(ciphertext) != k or k<hLen+2:
+ if len(ciphertext) != k or k < hLen+2:
raise ValueError("Ciphertext with incorrect length.")
# Step 2a (O2SIP)
ct_int = bytes_to_long(ciphertext)
@@ -171,8 +173,6 @@ class PKCS1OAEP_Cipher:
em = self._key._decrypt(ct_int)
# Step 3a
lHash = self._hashObj.new(self._label).digest()
- # Step 3b
- y = em[0]
# y must be 0, but we MUST NOT check it here in order not to
# allow attacks like Manger's (http://dl.acm.org/citation.cfm?id=704143)
maskedSeed = em[1:hLen+1]
@@ -185,19 +185,12 @@ class PKCS1OAEP_Cipher:
dbMask = self._mgf(seed, k-hLen-1)
# Step 3f
db = strxor(maskedDB, dbMask)
- # Step 3g
- one_pos = db[hLen:].find(b'\x01')
- lHash1 = db[:hLen]
- invalid = bord(y) | int(one_pos < 0)
- hash_compare = strxor(lHash1, lHash)
- for x in hash_compare:
- invalid |= bord(x)
- for x in db[hLen:one_pos]:
- invalid |= bord(x)
- if invalid != 0:
+ # Step 3b + 3g
+ res = oaep_decode(em, lHash, db)
+ if res <= 0:
raise ValueError("Incorrect decryption.")
# Step 4
- return db[hLen + one_pos + 1:]
+ return db[res:]
def new(key, hashAlgo=None, mgfunc=None, label=b'', randfunc=None):
"""Return a cipher object :class:`PKCS1OAEP_Cipher` that can be used to perform PKCS#1 OAEP encryption or decryption.
@@ -234,4 +227,3 @@ def new(key, hashAlgo=None, mgfunc=None,
if randfunc is None:
randfunc = Random.get_random_bytes
return PKCS1OAEP_Cipher(key, hashAlgo, mgfunc, label, randfunc)
-
--- a/lib/Crypto/Cipher/PKCS1_v1_5.py
+++ b/lib/Crypto/Cipher/PKCS1_v1_5.py
@@ -18,15 +18,18 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
-# ===================================================================
+# ==================================================================
__all__ = [ 'new', 'PKCS115_Cipher' ]
from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes
-from Crypto.Util.py3compat import bord, _copy_bytes
+from Crypto.Util.py3compat import bord, is_bytes, _copy_bytes
import Crypto.Util.number
from Crypto import Random
+from ._pkcs1_oaep_decode import pkcs1_decode
+
+
class PKCS115_Cipher:
"""This cipher can perform PKCS#1 v1.5 RSA encryption or decryption.
Do not instantiate directly. Use :func:`Crypto.Cipher.PKCS1_v1_5.new` instead."""
@@ -89,7 +92,6 @@ class PKCS115_Cipher:
continue
ps.append(new_byte)
ps = b"".join(ps)
- assert(len(ps) == k - mLen - 3)
# Step 2b
em = b'\x00\x02' + ps + b'\x00' + _copy_bytes(None, None, message)
# Step 3a (OS2IP)
@@ -100,7 +102,7 @@ class PKCS115_Cipher:
c = long_to_bytes(m_int, k)
return c
- def decrypt(self, ciphertext, sentinel):
+ def decrypt(self, ciphertext, sentinel, expected_pt_len=0):
r"""Decrypt a PKCS#1 v1.5 ciphertext.
This function is named ``RSAES-PKCS1-V1_5-DECRYPT``, and is specified in
@@ -167,13 +169,17 @@ class PKCS115_Cipher:
ct_int = bytes_to_long(ciphertext)
# Step 2b (RSADP) and Step 2c (I2OSP)
em = self._key._decrypt(ct_int)
- # Step 3
- sep = em.find(b'\x00', 2)
- if not em.startswith(b'\x00\x02') or sep < 10:
- return sentinel
- # Step 4
- return em[sep + 1:]
-
+ # Step 3 (not constant time when the sentinel is not a byte string)
+ output = bytes(bytearray(k))
+ if not is_bytes(sentinel) or len(sentinel) > k:
+ size = pkcs1_decode(em, b'', expected_pt_len, output)
+ if size < 0:
+ return sentinel
+ else:
+ return output[size:]
+ # Step 3 (somewhat constant time)
+ size = pkcs1_decode(em, sentinel, expected_pt_len, output)
+ return output[size:]
def new(key, randfunc=None):
"""Create a cipher for performing PKCS#1 v1.5 encryption or decryption.
--- /dev/null
+++ b/lib/Crypto/Cipher/_pkcs1_oaep_decode.py
@@ -0,0 +1,41 @@
+from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, c_size_t,
+ c_uint8_ptr)
+
+
+_raw_pkcs1_decode = load_pycryptodome_raw_lib("Crypto.Cipher._pkcs1_decode",
+ """
+ int pkcs1_decode(const uint8_t *em, size_t len_em,
+ const uint8_t *sentinel, size_t len_sentinel,
+ size_t expected_pt_len,
+ uint8_t *output);
+
+ int oaep_decode(const uint8_t *em,
+ size_t em_len,
+ const uint8_t *lHash,
+ size_t hLen,
+ const uint8_t *db,
+ size_t db_len);
+ """)
+
+
+def pkcs1_decode(em, sentinel, expected_pt_len, output):
+ if len(em) != len(output):
+ raise ValueError("Incorrect output length")
+
+ ret = _raw_pkcs1_decode.pkcs1_decode(c_uint8_ptr(em),
+ c_size_t(len(em)),
+ c_uint8_ptr(sentinel),
+ c_size_t(len(sentinel)),
+ c_size_t(expected_pt_len),
+ c_uint8_ptr(output))
+ return ret
+
+
+def oaep_decode(em, lHash, db):
+ ret = _raw_pkcs1_decode.oaep_decode(c_uint8_ptr(em),
+ c_size_t(len(em)),
+ c_uint8_ptr(lHash),
+ c_size_t(len(lHash)),
+ c_uint8_ptr(db),
+ c_size_t(len(db)))
+ return ret
--- a/lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py
+++ b/lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py
@@ -23,6 +23,7 @@
from __future__ import print_function
import json
+import sys
import unittest
from binascii import unhexlify
@@ -151,7 +152,10 @@ HKukWBcq9f/UOmS0oEhai/6g+Uf7VHJdWaeO5Lzu
pt_int = bytes_to_long(pt)
ct_int = self.key1024._encrypt(pt_int)
ct = long_to_bytes(ct_int, 128)
- self.assertEqual("---", cipher.decrypt(ct, "---"))
+ if sys.version_info[0] == 2:
+ self.assertEqual("---", cipher.decrypt(ct, "---"))
+ else:
+ self.assertEqual(b"", cipher.decrypt(ct, "---"))
def testEncryptVerify1(self):
# Encrypt/Verify messages of length [0..RSAlen-11]
--- a/lib/Crypto/Util/py3compat.py
+++ b/lib/Crypto/Util/py3compat.py
@@ -104,6 +104,11 @@ if sys.version_info[0] == 2:
def is_string(x):
return isinstance(x, basestring)
+ def is_bytes(x):
+ return isinstance(x, str) or \
+ isinstance(x, bytearray) or \
+ isinstance(x, memoryview)
+
ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()})
else:
@@ -146,6 +151,11 @@ else:
def is_string(x):
return isinstance(x, str)
+ def is_bytes(x):
+ return isinstance(x, bytes) or \
+ isinstance(x, bytearray) or \
+ isinstance(x, memoryview)
+
from abc import ABC
--- a/setup.py
+++ b/setup.py
@@ -381,6 +381,10 @@ ext_modules = [
include_dirs=['src/'],
sources=['src/cpuid.c']),
+ Extension("Crypto.Cipher._pkcs1_decode",
+ include_dirs=['src/'],
+ sources=['src/pkcs1_decode.c']),
+
# Chaining modes
Extension("Crypto.Cipher._raw_ecb",
include_dirs=['src/'],
--- /dev/null
+++ b/src/pkcs1_decode.c
@@ -0,0 +1,354 @@
+/* ===================================================================
+ *
+ * Copyright (c) 2021, Helder Eijs <helderijs@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * ===================================================================
+ */
+
+#include "common.h"
+
+FAKE_INIT(pkcs1_decode)
+
+#define SIZE_T_MAX ((size_t)-1)
+#define SIZE_T_LEN (sizeof(size_t))
+
+STATIC uint8_t rol8(uint8_t x)
+{
+ return (uint8_t)((x << 1) | (x >> 7));
+}
+
+/*
+ * Return 0 if x is 0, otherwise SIZE_T_MAX (all bits set to 1)
+ */
+STATIC size_t propagate_ones(uint8_t x)
+{
+ unsigned i;
+ uint8_t result8;
+ size_t result;
+
+ result8 = x;
+ for (i=0; i<8; i++) {
+ x = rol8(x);
+ result8 |= x;
+ }
+ result = 0;
+ for (i=0; i<sizeof(result); i++) {
+ result |= ((size_t)result8) << (i*8);
+ }
+
+ return result;
+}
+
+/*
+ * Set all bits to 1 in the flag if term1 == term2
+ * or leave the flag untouched otherwise.
+ */
+STATIC void set_if_match(uint8_t *flag, size_t term1, size_t term2)
+{
+ unsigned i;
+ uint8_t x;
+
+ x = 0;
+ for (i=0; i<sizeof(size_t); i++) {
+ x |= (uint8_t)((term1 ^ term2) >> (i*8));
+ }
+ *flag |= (uint8_t)~propagate_ones(x);
+}
+
+/*
+ * Set all bits to 1 in the flag if term1 != term2
+ * or leave the flag untouched otherwise.
+ */
+STATIC void set_if_no_match(uint8_t *flag, size_t term1, size_t term2)
+{
+ size_t i;
+ uint8_t x;
+
+ x = 0;
+ for (i=0; i<sizeof(size_t); i++) {
+ x |= (uint8_t)((term1 ^ term2) >> (i*8));
+ }
+ *flag |= (uint8_t)propagate_ones(x);
+}
+
+/*
+ * Copy in1[] into out[] if choice is 0, otherwise copy in2[]
+ */
+STATIC void safe_select(const uint8_t *in1, const uint8_t *in2, uint8_t *out, uint8_t choice, size_t len)
+{
+ size_t i;
+ uint8_t mask1, mask2;
+
+ mask1 = (uint8_t)propagate_ones(choice);
+ mask2 = (uint8_t)~mask1;
+ for (i=0; i<len; i++) {
+ out[i] = (in1[i] & mask2) | (in2[i] & mask1);
+ /* yes, these rol8s are redundant, but we try to avoid compiler optimizations */
+ mask1 = rol8(mask1);
+ mask2 = rol8(mask2);
+ }
+}
+
+/*
+ * Return in1 if choice is 0, in2 otherwise.
+ */
+STATIC size_t safe_select_idx(size_t in1, size_t in2, uint8_t choice)
+{
+ size_t mask;
+
+ mask = propagate_ones(choice);
+ return (in1 & ~mask) | (in2 & mask);
+}
+
+/*
+ * Return 0 if all these conditions hold:
+ * - in1[] is equal to in2[] where eq_mask[] is 0xFF,
+ * - in1[] is NOT equal to in2[] where neq_mask[] is 0xFF.
+ * Return non-zero otherwise.
+ */
+STATIC uint8_t safe_cmp_masks(const uint8_t *in1, const uint8_t *in2,
+ const uint8_t *eq_mask, const uint8_t *neq_mask,
+ size_t len)
+{
+ size_t i;
+ uint8_t c, result;
+
+ result = 0;
+ for (i=0; i<len; i++) {
+ c = (uint8_t)propagate_ones(*in1++ ^ *in2++);
+ result |= (uint8_t)(c & *eq_mask++); /* Set all bits to 1 if *in1 and *in2 differed
+ and eq_mask was 0xff */
+ result |= (uint8_t)(~c & *neq_mask++); /* Set all bits to 1 if *in1 and *in2 matched
+ and neq_mask was 0xff */
+ }
+
+ return result;
+}
+
+/*
+ * Return the index of the first byte with value c,
+ * the length of in1[] when c is not present,
+ * or SIZE_T_MAX in case of problems.
+ */
+STATIC size_t safe_search(const uint8_t *in1, uint8_t c, size_t len)
+{
+ size_t result, mask1, mask2, i;
+ uint8_t *in1_c;
+
+ if (NULL == in1 || 0 == len) {
+ return SIZE_T_MAX;
+ }
+
+ /*
+ * Create a second byte string and put c at the end,
+ * so that at least we will find it there.
+ */
+ in1_c = (uint8_t*) malloc(len + 1);
+ if (NULL == in1_c) {
+ return SIZE_T_MAX;
+ }
+ memcpy(in1_c, in1, len);
+ in1_c[len] = c;
+
+ result = 0;
+ mask2 = 0;
+ for (i=0; i<(len+1); i++) {
+ mask1 = ~mask2 & ~propagate_ones(in1_c[i] ^ c); /* Set mask1 to 0xff if there is a match
+ and it is the first one. */
+ result |= i & mask1;
+ mask2 |= mask1;
+ }
+
+ free(in1_c);
+ return result;
+}
+
+#define PKCS1_PREFIX_LEN 10
+
+/*
+ * Decode and verify the PKCS#1 padding, then put either the plaintext
+ * or the sentinel value into the output buffer, all in constant time.
+ *
+ * The output is a buffer of equal length as the encoded message (em).
+ *
+ * The sentinel is put into the buffer when decryption fails.
+ *
+ * The caller may already know the expected length of the plaintext M,
+ * which they should put into expected_pt_len.
+ * Otherwise, expected_pt_len must be set to 0.
+ *
+ * Either the plaintext or the sentinel will be put into the buffer
+ * with padding on the left.
+ *
+ * The function returns the number of bytes to ignore at the beginning
+ * of the output buffer, or -1 in case of problems.
+ */
+EXPORT_SYM int pkcs1_decode(const uint8_t *em, size_t len_em_output,
+ const uint8_t *sentinel, size_t len_sentinel,
+ size_t expected_pt_len,
+ uint8_t *output)
+{
+ size_t pos;
+ uint8_t match, selector;
+ uint8_t *padded_sentinel;
+ int result;
+
+ result = -1;
+
+ if (NULL == em || NULL == output || NULL == sentinel) {
+ return -1;
+ }
+ if (len_em_output < (PKCS1_PREFIX_LEN + 2)) {
+ return -1;
+ }
+ if (len_sentinel > len_em_output) {
+ return -1;
+ }
+ if (expected_pt_len > 0 && expected_pt_len > (len_em_output - PKCS1_PREFIX_LEN - 1)) {
+ return -1;
+ }
+
+ /** Pad sentinel (on the left) so that it matches em in length **/
+ padded_sentinel = (uint8_t*) calloc(1, len_em_output);
+ if (NULL == padded_sentinel) {
+ return -1;
+ }
+ memcpy(padded_sentinel + (len_em_output - len_sentinel), sentinel, len_sentinel);
+
+ /** The first 10 bytes must follow the pattern **/
+ match = safe_cmp_masks(em,
+ (const uint8_t*)"\x00\x02" "\x00\x00\x00\x00\x00\x00\x00\x00",
+ (const uint8_t*)"\xFF\xFF" "\x00\x00\x00\x00\x00\x00\x00\x00",
+ (const uint8_t*)"\x00\x00" "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
+ 10);
+
+ /*
+ * pos is the index of the first 0 byte. It is followed by the plaintext M.
+ * It can be (len_em_output-1) when the 0 is at the end (empty M).
+ * It can be len_em_output when the 0 is not present.
+ * It can SIZE_T_MAX in case of other errors.
+ */
+ pos = safe_search(em + 10, 0, len_em_output - 10) + 10;
+ if (pos == SIZE_T_MAX) {
+ result = -1;
+ goto end;
+ }
+
+ /*
+ * selector is 0 if:
+ * - there is a match for the first 10 bytes AND
+ * - a 0 byte is found in the remainder of em, AND
+ * - the length of the plaintext matches the expectation (if there is one)
+ */
+ selector = match;
+ set_if_match(&selector, pos, len_em_output);
+ if (expected_pt_len > 0) {
+ size_t pt_len;
+
+ pt_len = len_em_output - pos - 1;
+ set_if_no_match(&selector, pt_len, expected_pt_len);
+ }
+
+ /** Select the correct data to output **/
+ safe_select(em, padded_sentinel, output, selector, len_em_output);
+
+ /** Select the number of bytes that the caller will skip in output **/
+ result = (int)safe_select_idx(pos + 1, len_em_output - len_sentinel, selector);
+
+end:
+ free(padded_sentinel);
+ return result;
+}
+
+/*
+ * Decode and verify the OAEP padding in constant time.
+ *
+ * The function returns the number of bytes to ignore at the beginning
+ * of db (the rest is the plaintext), or -1 in case of problems.
+ */
+
+EXPORT_SYM int oaep_decode(const uint8_t *em,
+ size_t em_len,
+ const uint8_t *lHash,
+ size_t hLen,
+ const uint8_t *db,
+ size_t db_len) /* em_len - 1 - hLen */
+{
+ int result;
+ size_t one_pos, search_len, i;
+ uint8_t wrong_padding;
+ uint8_t *eq_mask = NULL;
+ uint8_t *neq_mask = NULL;
+ uint8_t *target_db = NULL;
+
+ if (NULL == em || NULL == lHash || NULL == db) {
+ return -1;
+ }
+
+ if (em_len < 2*hLen+2 || db_len != em_len-1-hLen) {
+ return -1;
+ }
+
+ /* Allocate */
+ eq_mask = (uint8_t*) calloc(1, db_len);
+ neq_mask = (uint8_t*) calloc(1, db_len);
+ target_db = (uint8_t*) calloc(1, db_len);
+ if (NULL == eq_mask || NULL == neq_mask || NULL == target_db) {
+ result = -1;
+ goto cleanup;
+ }
+
+ /* Step 3g */
+ search_len = db_len - hLen;
+
+ one_pos = safe_search(db + hLen, 0x01, search_len);
+ if (SIZE_T_MAX == one_pos) {
+ result = -1;
+ goto cleanup;
+ }
+
+ memset(eq_mask, 0xAA, db_len);
+ memcpy(target_db, lHash, hLen);
+ memset(eq_mask, 0xFF, hLen);
+
+ for (i=0; i<search_len; i++) {
+ eq_mask[hLen + i] = propagate_ones(i < one_pos);
+ }
+
+ wrong_padding = em[0];
+ wrong_padding |= safe_cmp_masks(db, target_db, eq_mask, neq_mask, db_len);
+ set_if_match(&wrong_padding, one_pos, search_len);
+
+ result = wrong_padding ? -1 : (int)(hLen + 1 + one_pos);
+
+cleanup:
+ free(eq_mask);
+ free(neq_mask);
+ free(target_db);
+
+ return result;
+}
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -96,6 +96,11 @@ build/clmul.o: ../ghash_clmul.c
build/test_clmul: test_clmul.c ../common.h build/clmul.o
$(CC) $(CFLAGS) -mssse3 -mpclmul $(CPPFLAGS) -o $@ $(filter %.c %.o, $^)
+# pkcs1
+
+build/pkcs1_decode.o: ../pkcs1_decode.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $^ -c
+
# Poly1305
build/poly1305.o: ../poly1305.c