NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/39525: cgd inadvertently encrypts blkno eight times to generate IV



>Number:         39525
>Category:       kern
>Synopsis:       cgd inadvertently encrypts blkno eight times to generate IV
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    kern-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Fri Sep 12 08:50:00 +0000 2008
>Originator:     Joachim Schueth, Frederik Sausmikat
>Release:        NetBSD 4.0
>Organization:
>Environment:
NetBSD pc50-s74.fhr.fgan.de 4.0 NetBSD 4.0 (GENERIC.MP.NOIPV6) #4: Thu Sep 11 
11:02:47 CEST 2008  
root%pc50-s74.fhr.fgan.de@localhost:/usr/obj/sys/arch/i386/compile/GENERIC.MP.NOIPV6
 i386
>Description:
cgd generates the IV for the encryption of each disc block by encrypting
the disc block number. Because a variable that holds the cipher block
length in bits is interpreted as a length in bytes, the disk block number
is encrypted eight times instead of just once, causing a loss in performance.
We assume that the intention of the cgd author was to encrypt the blkno
only once, since there is no comment concerning the different interpretation
of the length variable in the sources, and the documentation does not
mention an eightfold encryption.

In the case of AES, the cipher block length is 16 bytes. The encryption
or decryption of a 512 byte disc block therefore should require 
(512 / 16) + 1 = 33 AES operations, but cgd actually does 40 operations,
which theoretically reduces throughput by up to 17.5%. Experimentally we
observe a reduction of about 16% compared to a patched version of cgd
that encrypts the disk block number only once.

Note that unless the block cipher algorithm used is fundamentally flawed,
encrypting the disk block number several times does not provide better
security than encrypting it just once. Already a single encryption renders
the IV unpredictable to an attacker, preventing watermark attacks. The
biggest concern might be related plaintext attacks that exploit the fact
that the disk block numbers differ only in a few bits. But a cipher that
cannot be expected to resist such attacks should not be used anyways.

More esoteric scenarios where an attacker has write access to the cgd
device and can monitor the effects on the ciphertext side could allow the
attacker to use the device as an oracle to ECB encrypt arbitrary data
with the cgd key. But this is not prevented by changing the IV generation
method. An attack where the described oracle is used to compute IVs
is applicable in both cases (single or eightfold encryption of blkno).

Therefore, the performance gain comes at virtually no cost in security.
>How-To-Repeat:
You can verify that cgd in fact encrypts the blkno eight times by
running the following script:

----------snip--------------------------------------
# adjust these to unused devices on your system
CGD=cgd0
VND=vnd0

# cgd configuration - establishes key used below
cat >test.cgd <<EOF
algorithm aes-cbc;
iv-method encblkno;
keylength 256;
verify_method none;
keygen storedkey key AAABALcvpjo0FJqmzQr8GElHxGNnzf \
                     CE2Z8BSUDbT9kk2JlA;
EOF

# create cgd device via vnode
dd if=/dev/zero bs=512 count=2048 of=test.bin
vnconfig $VND test.bin
cgdconfig $CGD /dev/${VND}d test.cgd

# write zero plaintext (3 blocks) and read ciphertext
dd if=/dev/zero bs=16 count=3 of=/dev/${CGD}d 
cgdconfig -u $CGD
vnconfig -u $VND
dd if=test.bin bs=16 count=3 of=head.bin
dd if=test.bin bs=16 count=1 skip=2 of=aes_block_2.bin 2>/dev/null

# show hexdumps of data
alias hd='hexdump -e '\''16/1 " %02x" "\n"'\'''
echo "hexdump of beginning of encrypted sector:"
hd head.bin
echo "input for repeated decryption:"
hd aes_block_2.bin

# perform repeated decryption and show results
echo "outputs of repeated decryption:"
KEY=b72fa63a34149aa6cd0afc184947c46367cdf084d99f014940db4fd924d89940
IV=00000000000000000000000000000000
cp aes_block_2.bin in.bin
for i in 1 2 3 4 5 6 7 8 9 10 11 ; do
   openssl aes-256-ecb -d -nopad -K $KEY -iv $IV -in in.bin -out out.bin
   hd out.bin
   mv out.bin in.bin
done
----------snip--------------------------------------

This script analyses the cgd ciphertext of the first disk block after
writing zeros to the cgd device. Due to the structure of CBC, zero
plaintext means that each cipher block is the ECB encryption of its 
predecessor, and the first cipher block is the ECB encryption of the IV.
Thus, if one repeatedly decrypts a given cipher block, the sequence of
results is the reverse of the sequence of cipher blocks, until the IV is
reached. In the encblkno method for IV generation, one would expect
that the next decryption yields the disk block number, but for the
original cgd, the above script yields (comments added):

hexdump of beginning of encrypted sector:
 b5 54 29 b2 57 e6 ca 2f dd 13 7c a8 31 11 be 12 <-- C0
 b4 b4 35 25 5a c7 77 7d 26 c4 18 ea 6d 75 36 db <-- C1
 2e 2e b0 33 06 a8 39 c9 45 2b 55 46 64 45 7f b0 <-- C2
input for repeated decryption:
 2e 2e b0 33 06 a8 39 c9 45 2b 55 46 64 45 7f b0 <-- C2
outputs of repeated decryption:
 b4 b4 35 25 5a c7 77 7d 26 c4 18 ea 6d 75 36 db <-- C1
 b5 54 29 b2 57 e6 ca 2f dd 13 7c a8 31 11 be 12 <-- C0
 f6 8b 9f 5e 3c 3c 3a b7 f4 13 07 9b d9 c3 10 78 <-- IV
 64 13 a3 19 91 e6 c5 6a e0 4e 31 cf df a4 46 da <-- expected: blkno
 67 33 9e 57 e0 c7 a6 43 7c ef 4a 2d 76 99 44 09
 bc 3d 36 ab 5f 8b ae 0e eb d4 78 41 7d 37 36 03
 54 1e 90 40 4d 34 b1 76 ed 28 51 59 b5 bb 92 48
 ce 3e 2f 71 be 5b e3 3c 91 20 68 ec b4 74 de 0c
 60 02 8d 3f 58 7c 83 a3 37 36 00 88 79 f6 b2 c3
 31 36 65 77 47 1e 7d 0b 88 23 ba 19 f2 87 54 f7
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 <-- blkno after 8 steps

As can be seen, the blkno (which is zero) appears after decrypting
the IV eight times.

After applying the suggested patch to cgd (and changing encblkno to
encblkno2 in the above test script), the following output is obtained
(comments added):

hexdump of beginning of encrypted sector:
 60 02 8d 3f 58 7c 83 a3 37 36 00 88 79 f6 b2 c3 <-- C0
 ce 3e 2f 71 be 5b e3 3c 91 20 68 ec b4 74 de 0c <-- C1
 54 1e 90 40 4d 34 b1 76 ed 28 51 59 b5 bb 92 48 <-- C2
input for repeated decryption:
 54 1e 90 40 4d 34 b1 76 ed 28 51 59 b5 bb 92 48 <-- C2
outputs of repeated decryption:
 ce 3e 2f 71 be 5b e3 3c 91 20 68 ec b4 74 de 0c <-- C1
 60 02 8d 3f 58 7c 83 a3 37 36 00 88 79 f6 b2 c3 <-- C0
 31 36 65 77 47 1e 7d 0b 88 23 ba 19 f2 87 54 f7 <-- IV
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 <-- blkno
 [...]

For a benchmark, we ran the command

  dd if=/dev/zero of=/mnt/test.dat bs=1m count=100

on a 3.2 GHz Pentium 4 machine before and after applying the patch.
Typical data rates reported by dd were:

   43855123 bytes/sec (unpatched version)
   52219920 bytes/sec (patched, encblkno2)

These figures show a performance gain of 19%.
>Fix:
The easiest fix would be to simply replace the line 

   size_t               blocksize = cs->sc_cdata.cf_blocksize;

in cgd.c by     

   size_t               blocksize = cs->sc_cdata.cf_blocksize / 8;

However, this would not work with existing cgd volumes.
The patch provided below retains backwards compatibility by
introducing a new option "iv-method encblkno2" to cgdconfig.
Existing cgd volumes with "iv-method encblkno" in the config
file will still be handled as before.  When creating a new
cgd config file with cgdconfig, encblkno2 is now the default.
Depending on the option chosen, cgd will now either divide the
block length value by eight (to convert from bits to bytes)
or not (the old behavior).

----------cgd.patch---------------------------------
Index: cgd.c
===================================================================
RCS file: /cvsroot/src/sys/dev/cgd.c,v
retrieving revision 1.52
diff -u -r1.52 cgd.c
--- cgd.c       28 Apr 2008 20:23:46 -0000      1.52
+++ cgd.c       9 Sep 2008 14:29:11 -0000
@@ -516,12 +516,16 @@
                goto bail;
        }
 
-       /* right now we only support encblkno, so hard-code it */
        (void)memset(inbuf, 0, MAX_KEYSIZE);
        ret = copyinstr(ci->ci_ivmethod, inbuf, MAX_KEYSIZE, NULL);
        if (ret)
                goto bail;
-       if (strcmp("encblkno", inbuf)) {
+
+       if (strcmp("encblkno", inbuf) == 0)
+               cs->sc_cdata.cf_mode = CGD_CIPHER_CBC_ENCBLKNO;
+       else if (strcmp("encblkno2", inbuf) == 0)
+               cs->sc_cdata.cf_mode = CGD_CIPHER_CBC_ENCBLKNO2;
+       else {
                ret = EINVAL;
                goto bail;
        }
@@ -537,7 +541,6 @@
                goto bail;
 
        cs->sc_cdata.cf_blocksize = ci->ci_blocksize;
-       cs->sc_cdata.cf_mode = CGD_CIPHER_CBC_ENCBLKNO;
        cs->sc_cdata.cf_priv = cs->sc_cfuncs->cf_init(ci->ci_keylen, inbuf,
            &cs->sc_cdata.cf_blocksize);
        (void)memset(inbuf, 0, MAX_KEYSIZE);
@@ -722,7 +725,10 @@
        struct uio      srcuio;
        struct iovec    dstiov[2];
        struct iovec    srciov[2];
-       size_t          blocksize = cs->sc_cdata.cf_blocksize;
+
+       size_t blocksize = (cs->sc_cdata.cf_mode == CGD_CIPHER_CBC_ENCBLKNO) ?
+           cs->sc_cdata.cf_blocksize : cs->sc_cdata.cf_blocksize / 8;
+
        char            sink[blocksize];
        char            zero_iv[blocksize];
        char            blkno_buf[blocksize];
Index: cgdvar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/cgdvar.h,v
retrieving revision 1.11
diff -u -r1.11 cgdvar.h
--- cgdvar.h    28 Apr 2008 20:23:46 -0000      1.11
+++ cgdvar.h    9 Sep 2008 14:29:11 -0000
@@ -59,7 +59,8 @@
 struct cryptdata {
        size_t           cf_blocksize;  /* block size (in bytes) */
        int              cf_mode;       /* Cipher Mode and IV Gen method */
-#define CGD_CIPHER_CBC_ENCBLKNO 1      /* CBC Mode w/ Enc Block Number */
+#define CGD_CIPHER_CBC_ENCBLKNO  1     /* Enc Block Number (compat. mode) */
+#define CGD_CIPHER_CBC_ENCBLKNO2 2     /* CBC Mode w/ Enc Block Number */
        void            *cf_priv;       /* enc alg private data */
 };
----------snip--------------------------------------
 
----------cgd.4.patch-------------------------------
Index: cgd.4
===================================================================
RCS file: /cvsroot/src/share/man/man4/cgd.4,v
retrieving revision 1.10
diff -u -r1.10 cgd.4
--- cgd.4       30 Apr 2008 13:10:53 -0000      1.10
+++ cgd.4       9 Sep 2008 18:23:07 -0000
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd March 11, 2006
+.Dd September 8, 2008
 .Dt CGD 4
 .Os
 .Sh NAME
@@ -76,14 +76,26 @@
 The default key length is 128 bits.
 .El
 .Ss IV Methods
-Currently, the only IV Method supported is
-.Ar encblkno
-(Encrypted Block Number).
-This method encrypts the block number of the
-physical disk block with the cipher and key provided and uses that as the
-IV for CBC mode.
-This method should ensure that each block has a different
-IV and that the IV is reasonably unpredictable.
+Currently, the following IV Methods are supported:
+.Bl -tag -width encblkno2
+.It encblkno
+This is the original IV method used by
+.Nm
+and provided for backward compatibility. It repeatedly encrypts the block
+number of the physical disk block eight times and uses the result as the IV
+for CBC mode. This method should ensure that each block has a different IV
+and that the IV is reasonably unpredictable. The eightfold encryption was not
+intended and causes a notable performance loss with little (if any) increase
+in security over a single encryption.
+.It encblkno2
+This method  encrypts the block number of the physical disk block once with
+the cipher and key provided and uses the result as the IV for CBC mode. This
+method should ensure that each block has a different IV and that the IV is 
+reasonably unpredictable.
+This is the default method used by
+.Xr cgdconfig 8 when configuring new
+.Nm Ns 's .
+.El
 .Ss IOCTLS
 A
 .Nm
----------snip--------------------------------------

----------cgdconfig.patch---------------------------
Index: cgdconfig.8
===================================================================
RCS file: /cvsroot/src/sbin/cgdconfig/cgdconfig.8,v
retrieving revision 1.27
diff -u -r1.27 cgdconfig.8
--- cgdconfig.8 13 May 2008 09:31:06 -0000      1.27
+++ cgdconfig.8 9 Sep 2008 14:28:14 -0000
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd May 10, 2008
+.Dd September 8, 2008
 .Dt CGDCONFIG 8
 .Os
 .Sh NAME
@@ -97,7 +97,7 @@
 .It Fl g
 Generate a paramsfile (to stdout).
 .It Fl i Ar ivmeth
-Specify the IV method (default: encblkno).
+Specify the IV method (default: encblkno2).
 .It Fl k Ar kgmeth
 Specify the key generation method (default: pkcs5_pbkdf2/sha1).
 .It Fl o Ar outfile
@@ -316,7 +316,7 @@
 .Sh EXAMPLES
 To set up and configure a cgd that uses AES with a 192 bit key
 in CBC mode with the IV Method
-.Sq encblkno
+.Sq encblkno2
 (encrypted block number):
 .Bd -literal
        # cgdconfig -g -o /etc/cgd/wd0e aes-cbc 192
@@ -359,7 +359,7 @@
 An example parameters file which uses PKCS#5 PBKDF2:
 .Bd -literal
        algorithm aes-cbc;
-       iv-method encblkno;
+       iv-method encblkno2;
        keylength 128;
        verify_method none;
        keygen pkcs5_pbkdf2/sha1 {
@@ -372,7 +372,7 @@
 An example parameters file which stores its key locally:
 .Bd -literal
        algorithm       aes-cbc;
-       iv-method       encblkno;
+       iv-method       encblkno2;
        keylength       256;
        verify_method   none;
        keygen storedkey key AAABAK3QO6d7xzLfrXTdsgg4 \\
Index: params.c
===================================================================
RCS file: /cvsroot/src/sbin/cgdconfig/params.c,v
retrieving revision 1.23
diff -u -r1.23 params.c
--- params.c    11 May 2008 03:15:21 -0000      1.23
+++ params.c    9 Sep 2008 14:28:16 -0000
@@ -152,7 +152,7 @@
        if (p->verify_method == VERIFY_UNKNOWN)
                p->verify_method = VERIFY_NONE;
        if (!p->ivmeth)
-               p->ivmeth = string_fromcharstar("encblkno");
+               p->ivmeth = string_fromcharstar("encblkno2");
        if (p->keylen == (size_t)-1) {
                i = crypt_defaults_lookup(string_tocharstar(p->algorithm));
                if (i != (size_t)-1) {
@@ -196,7 +196,8 @@
                warnx("unspecified IV method");
                return 0;
        }
-       if (strcmp("encblkno", string_tocharstar(p->ivmeth)))
+       if (strcmp("encblkno", string_tocharstar(p->ivmeth)) &&
+           strcmp("encblkno2", string_tocharstar(p->ivmeth)))
                warnx("unknown IV method \"%s\" (warning)",
                    string_tocharstar(p->ivmeth));
        if (p->keylen == (size_t)-1) {
----------snip--------------------------------------

All patches are against NetBSD-CURRENT.



Home | Main Index | Thread Index | Old Index