what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

IBM SPSS 21 Weak Cryptography Implementation

IBM SPSS 21 Weak Cryptography Implementation
Posted Dec 14, 2013
Authored by Ben Pfaff

IBM SPSS2 21 and later suffer from use of an encryption scheme with inherent weaknesses.

tags | advisory
SHA-256 | c58972c405ed50c8cc26d0d54d3e71001da9a74b2fb837e0a855029df5beba68

IBM SPSS 21 Weak Cryptography Implementation

Change Mirror Download
IBM SPSS 21 and later support a form of encrypted data files.  The
cryptographic scheme used, which is not publicly documented, has
recently come to my attention. It is flawed enough that I feel I must
present it publicly.

The encrypted data file format is identical to the pre-existing
plaintext file format (which isn't important here), except that each
16-byte block is encrypted with AES-256 in ECB mode. The AES-256 key
is derived from a password by a single AES-256 CMAC operation, as:

CMAC-AES-256(password, constant)

where password is the literal password typed by the user (padded on
the right with zeros to fill out a 32-byte AES-256 key, since CMAC
needs a real cryptographic key not just any random string of bytes
like HMAC) and constant is a particular 73-byte constant. This only
produces a 16-byte result. AES-256 needs a 32-byte key, so the
16-byte result is repeated twice to expand it to 32 bytes.

(I think that the authors of the implementation must have thought they
were doing something smart, because the 73-byte constant is in the
right form for the NIST SP 800-108 key derivation function in counter
mode. But that KDF is meant for deriving one cryptographic key from
another, not from a password.)

The problems I see:

- ECB mode.

- Cheap password derivation function (single round of CMAC)
instead of an intentionally expensive function like PBKDF2 with
thousands of iterations.

- No salt, and the first 16 bytes of plaintext are essentially
constant (as a magic number). I believe that this means rainbow
tables are possible.

- Password is silently truncated after 10 bytes, limiting actual
entropy in the key to 80 bits at the very most and probably more
like 40 to 60 bits realistically. (AES-256 is obviously
overkill.)

Other issues:

- Governments, universities, and companies use SPSS to analyze
survey data that sometimes contain people's personal information
that must not become public, so confidentiality is actually
important here. Mitigating that a little, this encrypted format
is new in the last year or two, and a lot of organizations don't
upgrade SPSS frequently because it is very expensive, so the
encrypted format may not be widely used yet.

- SPSS documentation talks about "encrypted passwords" that can be
used in SPSS program syntax in place of plaintext passwords.
However, calling these passwords "encrypted" is a misnomer,
because the encoding algorithm is simple, fixed, and unkeyed.

I'm appending a program that decrypts such a data file, given the
plaintext or "encrypted" password. Compile and link against libcrypto
(from OpenSSL).

--8<--------------------------cut here-------------------------->8--

#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

#include <openssl/aes.h>
#include <openssl/cmac.h>

/* Initializes AES from PASSWORD. Returns true if CIPHERTEXT is the first
ciphertext block in an encrypted .sav file for PASSWORD, false if PASSWORD
is wrong. */
static bool
init(const char *password, const uint8_t ciphertext[16], AES_KEY *aes)
{
/* NIST SP 800-108 fixed data. */
static const uint8_t fixed[] = {
/* i */
0x00, 0x00, 0x00, 0x01,

/* label */
0x35, 0x27, 0x13, 0xcc, 0x53, 0xa7, 0x78, 0x89,
0x87, 0x53, 0x22, 0x11, 0xd6, 0x5b, 0x31, 0x58,
0xdc, 0xfe, 0x2e, 0x7e, 0x94, 0xda, 0x2f, 0x00,
0xcc, 0x15, 0x71, 0x80, 0x0a, 0x6c, 0x63, 0x53,

/* delimiter */
0x00,

/* context */
0x38, 0xc3, 0x38, 0xac, 0x22, 0xf3, 0x63, 0x62,
0x0e, 0xce, 0x85, 0x3f, 0xb8, 0x07, 0x4c, 0x4e,
0x2b, 0x77, 0xc7, 0x21, 0xf5, 0x1a, 0x80, 0x1d,
0x67, 0xfb, 0xe1, 0xe1, 0x83, 0x07, 0xd8, 0x0d,

/* L */
0x00, 0x00, 0x01, 0x00,
};

char padded_password[32];
uint8_t plaintext[16];
size_t password_len;
uint8_t cmac[16];
uint8_t key[32];
size_t cmac_len;
CMAC_CTX *ctx;
int retval;

/* Truncate password to at most 10 bytes. */
password_len = strlen (password);
if (password_len > 10)
password_len = 10;

/* padded_password = password padded with zeros to 32 bytes. */
memset (padded_password, 0, sizeof padded_password);
memcpy (padded_password, password, password_len);

/* cmac = CMAC(padded_password, fixed). */
ctx = CMAC_CTX_new ();
assert (ctx != NULL);

retval = CMAC_Init (ctx, padded_password, sizeof padded_password,
EVP_aes_256_cbc (), NULL);
assert (retval == 1);

retval = CMAC_Update (ctx, fixed, sizeof fixed);
assert (retval == 1);

cmac_len = sizeof cmac;
retval = CMAC_Final (ctx, cmac, &cmac_len);
assert (retval == 1);
assert (cmac_len == 16);

/* The key is the cmac repeated twice. */
memcpy(key, cmac, 16);
memcpy(key + 16, cmac, 16);

/* Use key to initialize AES. */
assert (sizeof key == 32);
retval = AES_set_decrypt_key (key, sizeof key * 8, aes);
assert (retval >= 0);

/* Check for magic number "$FL" always present in SPSS .sav file. */
AES_ecb_encrypt (ciphertext, plaintext, aes, AES_DECRYPT);
return !memcmp (plaintext, "$FL", 3);
}

/* Password decoding. */

#define b(x) (1 << (x))

static const uint16_t m0[4][2] = {
{ b(2), b(2) | b(3) | b(6) | b(7) },
{ b(3), b(0) | b(1) | b(4) | b(5) },
{ b(4) | b(7), b(8) | b(9) | b(12) | b(14) },
{ b(5) | b(6), b(10) | b(11) | b(14) | b(15) },
};

static const uint16_t m1[4][2] = {
{ b(0) | b(3) | b(12) | b(15), b(0) | b(1) | b(4) | b(5) },
{ b(1) | b(2) | b(13) | b(14), b(2) | b(3) | b(6) | b(7) },
{ b(4) | b(7) | b(8) | b(11), b(8) | b(9) | b(12) | b(13) },
{ b(5) | b(6) | b(9) | b(10), b(10) | b(11) | b(14) | b(15) },
};

static const uint16_t m2[4][2] = {
{ b(2), b(1) | b(3) | b(9) | b(11) },
{ b(3), b(0) | b(2) | b(8) | b(10) },
{ b(4) | b(7), b(4) | b(6) | b(12) | b(14) },
{ b(5) | b(6), b(5) | b(7) | b(13) | b(15) },
};

static const uint16_t m3[4][2] = {
{ b(0) | b(3) | b(12) | b(15), b(0) | b(2) | b(8) | b(10) },
{ b(1) | b(2) | b(13) | b(14), b(1) | b(3) | b(9) | b(11) },
{ b(4) | b(7) | b(8) | b(11), b(4) | b(6) | b(12) | b(14) },
{ b(5) | b(6) | b(9) | b(10), b(5) | b(7) | b(13) | b(15) },
};

static int
decode_nibble (const uint16_t table[4][2], int nibble)
{
int i;

for (i = 0; i < 4; i++)
if (table[i][0] & (1 << nibble))
return table[i][1];

return 0;
}

/* Returns true if X has exactly one 1-bit, false otherwise. */
static bool
is_pow2 (int x)
{
return x && (x & (x - 1)) == 0;
}

/* If X has exactly one 1-bit, returns its index, where bit 0 is the LSB.
Otherwise, returns 0. */
static int
find_1bit (uint16_t x)
{
int i;

if (!is_pow2 (x))
return -1;

for (i = 0; i < 16; i++)
if (x & (1u << i))
return i;

abort ();
}

/* Attempts to decode a pair of encoded password characters A and B into a
single byte of the plaintext password. Returns 0 if A and B are not a valid
encoded password pair, otherwise a byte of the plaintext password. */
static int
decode_password_2bytes (uint8_t a, uint8_t b)
{
int x = find_1bit (decode_nibble (m0, a >> 4) & decode_nibble (m2, b >> 4));
int y = find_1bit (decode_nibble (m1, a & 15) & decode_nibble (m3, b & 15));
return x < 0 || y < 0 ? 0 : (x << 4) | y;
}

/* Decodes an SPSS so-called "encrypted" password INPUT into OUTPUT.

An encoded password is always an even number of bytes long and no longer
than 20 bytes. A decoded password is never longer than 10 bytes plus a null
terminator.

Returns true if successful, otherwise false. */
static bool
decode_password (const char *input, char output[11])
{
size_t len;

len = strlen (input);
if (len > 20 || len % 2)
return false;

for (; *input; input += 2)
{
int c = decode_password_2bytes (input[0], input[1]);
if (!c)
return false;
*output++ = c;
}
*output = '\0';

return true;
}

/* Main program. */

static void
xfread (void *data, size_t size, size_t count, FILE *stream)
{
if (fread (data, size, count, stream) != count)
{
if (ferror (stream))
fprintf (stderr, "read error (%s)", strerror (errno));
else
fprintf (stderr, "unexpected end of input\n");
exit (1);
}
}

int
main (int argc, char *argv[])
{
uint8_t in[36];
char pw[11];
AES_KEY aes;

if (argc != 2)
{
fprintf (stderr,
"usage: %s PASSWORD < ENCRYPTED > DECRYPTED\n"
"where ENCRYPTED is an encrypted system file,\n"
" DECRYPTED is the decrypted version,\n"
" and PASSWORD is the plaintext or encoded password.\n",
argv[0]);
exit (1);
}
if (isatty (1))
{
fprintf (stderr, "not writing output to a terminal, please "
"redirect to a file\n");
exit (1);
}

/* Read and discard 36-byte header, which only serves as a magic number. */
xfread (in, 36, 1, stdin);

/* Read first ciphertext block and use it to verify the password. Try the
password as plaintext first, then try decoding it. */
xfread (in, 16, 1, stdin);
if (!init (argv[1], in, &aes)
&& !(decode_password (argv[1], pw) && init (pw, in, &aes)))
{
fprintf (stderr, "wrong password, sorry\n");
exit (1);
}

/* Decrypt entire input. */
do
{
uint8_t out[16];

AES_ecb_encrypt (in, out, &aes, AES_DECRYPT);
fwrite (out, 16, 1, stdout);
}
while (fread (in, 16, 1, stdin));

return 0;
}

/*
Local variables:
compile-command: "gcc -Wall -Wextra decrypt.c -o decrypt -lcrypto"
End:
*/


Login or Register to add favorites

File Archive:

April 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Apr 1st
    10 Files
  • 2
    Apr 2nd
    26 Files
  • 3
    Apr 3rd
    40 Files
  • 4
    Apr 4th
    6 Files
  • 5
    Apr 5th
    26 Files
  • 6
    Apr 6th
    0 Files
  • 7
    Apr 7th
    0 Files
  • 8
    Apr 8th
    22 Files
  • 9
    Apr 9th
    14 Files
  • 10
    Apr 10th
    10 Files
  • 11
    Apr 11th
    13 Files
  • 12
    Apr 12th
    14 Files
  • 13
    Apr 13th
    0 Files
  • 14
    Apr 14th
    0 Files
  • 15
    Apr 15th
    30 Files
  • 16
    Apr 16th
    10 Files
  • 17
    Apr 17th
    22 Files
  • 18
    Apr 18th
    45 Files
  • 19
    Apr 19th
    8 Files
  • 20
    Apr 20th
    0 Files
  • 21
    Apr 21st
    0 Files
  • 22
    Apr 22nd
    11 Files
  • 23
    Apr 23rd
    68 Files
  • 24
    Apr 24th
    23 Files
  • 25
    Apr 25th
    0 Files
  • 26
    Apr 26th
    0 Files
  • 27
    Apr 27th
    0 Files
  • 28
    Apr 28th
    0 Files
  • 29
    Apr 29th
    0 Files
  • 30
    Apr 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close