Mission: Difficult [encrypt/obfuscate 9-digit SSN into 20 chars or less]

D

Drebin

It's a long story really, but the bottom line is we need to encrypt or
obfuscate a clear-text 9-digit SSN/taxpayer ID into something less than 21
characters. It doesn't need to be super-secure, just something that isn't
plain-text and it HAS to be as unique as the original number. It also does
not need to be a symmetric algorithm - we are using this as a way to create
a unique "userid" on a system to which we single-signon. So it's used
programmatically only.

DES/3DES/RC2 are 24 characters, MD5 is 24 (I think?) and SHA1 is 40 - so
those are out

So then, I was just going to do an XOR "encryption" (and I use that word
lightly) - but you'd have to use a second unique value, for each SSN - to
not have this become basically a character swap. In other words, if I use
one "key", then "9" always translates to A - for ALL SSN's.. which is bad,
because it's very easy to pick out the pattern.

SO.. anyone have any ideas of how to mangle up a 9-digit SSN in 20 chars or
less?? Maybe I'm overthinking the problem somehow?
 
N

Nicholas Paldino [.NET/C# MVP]

Drebin,

You could always use the MD5 hash algorithm (using the MD5 class or the
MD5CryptoProvider in the System.Security.Cryptography namespace. This will
produce a 128 bit hash code, which you can store in 8 unicode characters.

Hope this helps.
 
T

Tim Gallivan

I've done this before: randomly generate a one-time pad each type you need
to encrypt, use it to encrypt your characters, break up the pad into single
characters and stick it inside the encrypted string along with dummy
characters. Your decryption code will know the pad and what to decrypt. You
can vary this by changing the order of the pad and/or the data, by using
other dummy values. It all depends on how secure you need to be.
 
J

Jon Skeet [C# MVP]

Drebin said:
It's a long story really, but the bottom line is we need to encrypt or
obfuscate a clear-text 9-digit SSN/taxpayer ID into something less than 21
characters. It doesn't need to be super-secure, just something that isn't
plain-text and it HAS to be as unique as the original number. It also does
not need to be a symmetric algorithm - we are using this as a way to create
a unique "userid" on a system to which we single-signon. So it's used
programmatically only.

DES/3DES/RC2 are 24 characters, MD5 is 24 (I think?) and SHA1 is 40 - so
those are out

So then, I was just going to do an XOR "encryption" (and I use that word
lightly) - but you'd have to use a second unique value, for each SSN - to
not have this become basically a character swap. In other words, if I use
one "key", then "9" always translates to A - for ALL SSN's.. which is bad,
because it's very easy to pick out the pattern.

SO.. anyone have any ideas of how to mangle up a 9-digit SSN in 20 chars or
less?? Maybe I'm overthinking the problem somehow?

Firstly, you need to get the terminology precise - do you mean 20
characters, or 20 bytes? Are the 9 digits just that - digits, 0-9 (and
therefore representable as 5 bytes)?

DES will encrypt 5 bytes (or more easily, 8 bytes) into 8 bytes, and
then you could use base-64 to go from that to a 12 character form.

Here's some sample code to do just that. Note that it will encrypt
"000123" in the same way as "00123" (and decrypt them both as "123").
If that's a problem, you'll need to think of a slightly different way
of going from the ID to a long, but I'm sure it'll be doable.


using System;
using System.IO;
using System.Security.Cryptography;

class Test
{
static void Main()
{
// In real code you'd need to set an IV and Key to be the same
// thing each time, of course
DESCryptoServiceProvider csp = new DESCryptoServiceProvider();


string encrypted = Encrypt (csp, "123456789");

Console.WriteLine ("Encrypted version: {0}", encrypted);

string plain = Decrypt (csp, encrypted);
Console.WriteLine ("Plain text: {0}", plain);
}

static string Encrypt(DESCryptoServiceProvider csp, string plain)
{
long number = long.Parse(plain);
byte[] bytes = BitConverter.GetBytes(number);
ICryptoTransform trans = csp.CreateEncryptor();

using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream
(ms, trans, CryptoStreamMode.Write))
{
cs.Write(bytes, 0, bytes.Length);
cs.Close();
}

return Convert.ToBase64String (ms.ToArray());
}
}

static string Decrypt (DESCryptoServiceProvider csp,
string encrypted)
{
byte[] encryptedBytes = Convert.FromBase64String(encrypted);

ICryptoTransform trans = csp.CreateDecryptor();

using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream
(ms, trans, CryptoStreamMode.Write))
{
cs.Write(encryptedBytes, 0, encryptedBytes.Length);
cs.Close();
}

byte[] decryptedBytes = ms.ToArray();
if (decryptedBytes.Length != 8)
{
throw new ArgumentException ("encrypted",
"Invalid data");
}
return BitConverter.ToInt64(decryptedBytes, 0).ToString();
}
}
}
 
J

Jon Skeet [C# MVP]

Nicholas Paldino said:
You could always use the MD5 hash algorithm (using the MD5 class or the
MD5CryptoProvider in the System.Security.Cryptography namespace. This will
produce a 128 bit hash code, which you can store in 8 unicode characters.

It won't be definitely unique though... Admittedly the chances of it
not being unique are tiny, but using a two-way scheme guarantees
uniqueness here.
 
D

Drebin

See inline...

Jon Skeet said:
Firstly, you need to get the terminology precise - do you mean 20
characters, or 20 bytes? Are the 9 digits just that - digits, 0-9 (and
therefore representable as 5 bytes)?

This is in a database, and then the 20 chars we're send are in an XML doc -
so assume everything is CHARACTERS.

These are standard U.S. social security numbers, so yes - nine digits that
are 0 through 9. What do you mean representable as 5 bytes??
DES will encrypt 5 bytes (or more easily, 8 bytes) into 8 bytes, and
then you could use base-64 to go from that to a 12 character form.

Here's some sample code to do just that. Note that it will encrypt
"000123" in the same way as "00123" (and decrypt them both as "123").
If that's a problem, you'll need to think of a slightly different way
of going from the ID to a long, but I'm sure it'll be doable.

Lemme take a look at this... thanks much!!
using System;
using System.IO;
using System.Security.Cryptography;

class Test
{
static void Main()
{
// In real code you'd need to set an IV and Key to be the same
// thing each time, of course
DESCryptoServiceProvider csp = new DESCryptoServiceProvider();


string encrypted = Encrypt (csp, "123456789");

Console.WriteLine ("Encrypted version: {0}", encrypted);

string plain = Decrypt (csp, encrypted);
Console.WriteLine ("Plain text: {0}", plain);
}

static string Encrypt(DESCryptoServiceProvider csp, string plain)
{
long number = long.Parse(plain);
byte[] bytes = BitConverter.GetBytes(number);
ICryptoTransform trans = csp.CreateEncryptor();

using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream
(ms, trans, CryptoStreamMode.Write))
{
cs.Write(bytes, 0, bytes.Length);
cs.Close();
}

return Convert.ToBase64String (ms.ToArray());
}
}

static string Decrypt (DESCryptoServiceProvider csp,
string encrypted)
{
byte[] encryptedBytes = Convert.FromBase64String(encrypted);

ICryptoTransform trans = csp.CreateDecryptor();

using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream
(ms, trans, CryptoStreamMode.Write))
{
cs.Write(encryptedBytes, 0, encryptedBytes.Length);
cs.Close();
}

byte[] decryptedBytes = ms.ToArray();
if (decryptedBytes.Length != 8)
{
throw new ArgumentException ("encrypted",
"Invalid data");
}
return BitConverter.ToInt64(decryptedBytes, 0).ToString();
}
}
}
 
D

Drebin

Well, it's not necessarily bad - but when this vendor (although it's a
trusted vendor) sees a bunch of (for example, 5 SSN's that start with "011"
for example)

aab******
aab******
aab******
aab******
aab******

type records, they will know it's basically a character replacement.. and
again, they are a trusted vendor, but we'd rather not have our SSN's or an
easily-discoverable version of their SSN available to them.

Thanks though

Dennis Myrén said:
I guess ROT[BASE] is to simple?

--
Regards,
Dennis JD Myrén
Oslo Kodebureau
Drebin said:
It's a long story really, but the bottom line is we need to encrypt or
obfuscate a clear-text 9-digit SSN/taxpayer ID into something less than 21
characters. It doesn't need to be super-secure, just something that isn't
plain-text and it HAS to be as unique as the original number. It also does
not need to be a symmetric algorithm - we are using this as a way to
create
a unique "userid" on a system to which we single-signon. So it's used
programmatically only.

DES/3DES/RC2 are 24 characters, MD5 is 24 (I think?) and SHA1 is 40 - so
those are out

So then, I was just going to do an XOR "encryption" (and I use that word
lightly) - but you'd have to use a second unique value, for each SSN - to
not have this become basically a character swap. In other words, if I use
one "key", then "9" always translates to A - for ALL SSN's.. which is bad,
because it's very easy to pick out the pattern.

SO.. anyone have any ideas of how to mangle up a 9-digit SSN in 20 chars
or
less?? Maybe I'm overthinking the problem somehow?
 
A

Alberto Salvati

Hi, Drebin.
I dont know if SSN is numeric or alphanumeric code. If it's numeric
think a code like:

1) make an array or list that contains chars from "0" to "9" in a random
but *fixed* order:

2,3,1,0,4,5,9,8,6,7



2) loop on YourNumericCode; for each char in SSN get position index in
array (see prev point)

3) you need 5 bit to descrive decimal value "9". Now, convert char index
in binary format and add result to a string/stringbuilder

4) For each iteration you've


0001
00011000
000110000100
....

iterate on this string, get 4 char, convert this substring in a hex code

9 (ssn length) * 4 bit = 36 bits
36 bits/8 => 6 hex chars

finally use some simple algo to make a check digit and put it as last
char of resulting string.

If you want, you can add other "garbage" data to result...

you can add a "seed" code based on value from 1 to 255 (ff).
For each encode operation you can increment this value, and when it's
euqal to 255, reset it to zero...


you can change real code position using random defined char as tag
start/end code..


Is ssn is *alphabetic*, you can use this login simply using 5 bits
insthead 4 (26 d, 1a hex, 11010 binary)
(26= a,b,c,d,..w,x,y,z)

Finally, if ssn is *alphanumeric*, you can use this login simply using 6
bits

26 letters (abcdefg...)
10 numeric symbols (0,1,2,3
max value = 36 (24hex, 100100 binary)


HTH

Alberto Salvati
 
D

Drebin

Jon,

Thanks for your help, but this is what I ran into before (regarding your
code below) - for "12345678", this returns:

Qxrgm9Le6ljQ/seKzR+rbg==

Which is 24 characters.... ???


Jon Skeet said:
Drebin said:
It's a long story really, but the bottom line is we need to encrypt or
obfuscate a clear-text 9-digit SSN/taxpayer ID into something less than 21
characters. It doesn't need to be super-secure, just something that isn't
plain-text and it HAS to be as unique as the original number. It also does
not need to be a symmetric algorithm - we are using this as a way to create
a unique "userid" on a system to which we single-signon. So it's used
programmatically only.

DES/3DES/RC2 are 24 characters, MD5 is 24 (I think?) and SHA1 is 40 - so
those are out

So then, I was just going to do an XOR "encryption" (and I use that word
lightly) - but you'd have to use a second unique value, for each SSN - to
not have this become basically a character swap. In other words, if I use
one "key", then "9" always translates to A - for ALL SSN's.. which is bad,
because it's very easy to pick out the pattern.

SO.. anyone have any ideas of how to mangle up a 9-digit SSN in 20 chars or
less?? Maybe I'm overthinking the problem somehow?

Firstly, you need to get the terminology precise - do you mean 20
characters, or 20 bytes? Are the 9 digits just that - digits, 0-9 (and
therefore representable as 5 bytes)?

DES will encrypt 5 bytes (or more easily, 8 bytes) into 8 bytes, and
then you could use base-64 to go from that to a 12 character form.

Here's some sample code to do just that. Note that it will encrypt
"000123" in the same way as "00123" (and decrypt them both as "123").
If that's a problem, you'll need to think of a slightly different way
of going from the ID to a long, but I'm sure it'll be doable.


using System;
using System.IO;
using System.Security.Cryptography;

class Test
{
static void Main()
{
// In real code you'd need to set an IV and Key to be the same
// thing each time, of course
DESCryptoServiceProvider csp = new DESCryptoServiceProvider();


string encrypted = Encrypt (csp, "123456789");

Console.WriteLine ("Encrypted version: {0}", encrypted);

string plain = Decrypt (csp, encrypted);
Console.WriteLine ("Plain text: {0}", plain);
}

static string Encrypt(DESCryptoServiceProvider csp, string plain)
{
long number = long.Parse(plain);
byte[] bytes = BitConverter.GetBytes(number);
ICryptoTransform trans = csp.CreateEncryptor();

using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream
(ms, trans, CryptoStreamMode.Write))
{
cs.Write(bytes, 0, bytes.Length);
cs.Close();
}

return Convert.ToBase64String (ms.ToArray());
}
}

static string Decrypt (DESCryptoServiceProvider csp,
string encrypted)
{
byte[] encryptedBytes = Convert.FromBase64String(encrypted);

ICryptoTransform trans = csp.CreateDecryptor();

using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream
(ms, trans, CryptoStreamMode.Write))
{
cs.Write(encryptedBytes, 0, encryptedBytes.Length);
cs.Close();
}

byte[] decryptedBytes = ms.ToArray();
if (decryptedBytes.Length != 8)
{
throw new ArgumentException ("encrypted",
"Invalid data");
}
return BitConverter.ToInt64(decryptedBytes, 0).ToString();
}
}
}
 
D

Drebin

Tim, thanks for your help.. I read and re-read, but I don't think I get what
you mean here. Do you have an example or code snippet? And when you say
"encrypt" - using which algorithm??
 
J

Jon Skeet [C# MVP]

Drebin said:
This is in a database, and then the 20 chars we're send are in an XML doc -
so assume everything is CHARACTERS.

Right, good.
These are standard U.S. social security numbers, so yes - nine digits that
are 0 through 9. What do you mean representable as 5 bytes??

Well, with 9 digits, there are only 10^9 possibilities - basically
every number from
000000000
to
999999999

(plus distinctions between 012 and 12, for instance, but let's leave
that for the moment).

In fact, I made a mistake before - 9 digits can be represented as a 32-
bit integer easily (look at how big Int32.MaxValue is). I'm suggesting
that instead of thinking of the SSN as a string of digits, you think of
it as an integer. You're basically trying to encrypt a 32-bit integer,
which is much easier than thinking of it as a 9 character string :)
 
D

Drebin

I chuckled.. that was pretty smart!!! :)

Jon Skeet said:
Right, good.


Well, with 9 digits, there are only 10^9 possibilities - basically
every number from
000000000
to
999999999

(plus distinctions between 012 and 12, for instance, but let's leave
that for the moment).

In fact, I made a mistake before - 9 digits can be represented as a 32-
bit integer easily (look at how big Int32.MaxValue is). I'm suggesting
that instead of thinking of the SSN as a string of digits, you think of
it as an integer. You're basically trying to encrypt a 32-bit integer,
which is much easier than thinking of it as a 9 character string :)
 
D

Drebin

Disregard, I'm an idiot.. I needed to translated this into VB.NET (because
the guy who's going to support this hates C#).. I accidentally converted
this "number" to a long instead of int.. this works now.. so 1234567890
encrypts to:

DNdt2q3Jhsw=

which is 12 characters - and that's perfect!! Thanks much!! :)


Drebin said:
Jon,

Thanks for your help, but this is what I ran into before (regarding your
code below) - for "12345678", this returns:

Qxrgm9Le6ljQ/seKzR+rbg==

Which is 24 characters.... ???


than
chars
or
less?? Maybe I'm overthinking the problem somehow?

Firstly, you need to get the terminology precise - do you mean 20
characters, or 20 bytes? Are the 9 digits just that - digits, 0-9 (and
therefore representable as 5 bytes)?

DES will encrypt 5 bytes (or more easily, 8 bytes) into 8 bytes, and
then you could use base-64 to go from that to a 12 character form.

Here's some sample code to do just that. Note that it will encrypt
"000123" in the same way as "00123" (and decrypt them both as "123").
If that's a problem, you'll need to think of a slightly different way
of going from the ID to a long, but I'm sure it'll be doable.


using System;
using System.IO;
using System.Security.Cryptography;

class Test
{
static void Main()
{
// In real code you'd need to set an IV and Key to be the same
// thing each time, of course
DESCryptoServiceProvider csp = new DESCryptoServiceProvider();


string encrypted = Encrypt (csp, "123456789");

Console.WriteLine ("Encrypted version: {0}", encrypted);

string plain = Decrypt (csp, encrypted);
Console.WriteLine ("Plain text: {0}", plain);
}

static string Encrypt(DESCryptoServiceProvider csp, string plain)
{
long number = long.Parse(plain);
byte[] bytes = BitConverter.GetBytes(number);
ICryptoTransform trans = csp.CreateEncryptor();

using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream
(ms, trans, CryptoStreamMode.Write))
{
cs.Write(bytes, 0, bytes.Length);
cs.Close();
}

return Convert.ToBase64String (ms.ToArray());
}
}

static string Decrypt (DESCryptoServiceProvider csp,
string encrypted)
{
byte[] encryptedBytes = Convert.FromBase64String(encrypted);

ICryptoTransform trans = csp.CreateDecryptor();

using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream
(ms, trans, CryptoStreamMode.Write))
{
cs.Write(encryptedBytes, 0, encryptedBytes.Length);
cs.Close();
}

byte[] decryptedBytes = ms.ToArray();
if (decryptedBytes.Length != 8)
{
throw new ArgumentException ("encrypted",
"Invalid data");
}
return BitConverter.ToInt64(decryptedBytes, 0).ToString();
}
}
}
 
J

Jon Skeet [C# MVP]

Drebin said:
Thanks for your help, but this is what I ran into before (regarding your
code below) - for "12345678", this returns:

Qxrgm9Le6ljQ/seKzR+rbg==

Which is 24 characters.... ???

Oops - didn't check :)

I'm slightly surprised by that, as I thought 8 bytes would still be
encrypted as 8 bytes, which would then become 12 characters.

However, due to my previous mistake about numbers, it's probably
easiest just to convert it to int.Parse, BitConverter.ToInt32, and
check that the decrypted value is 4 bytes long, etc.

That works, giving "TP3IWR2LXJs=" for 123456789.
 
J

Jon Skeet [C# MVP]

Drebin said:
All set now, that was my mistake.. thanks again for you help!! :)

Nope, it wasn't your mistake at all - it was a problem in my code as
presented to you. I think you accurately converted the code :)
 
N

Nick Malik

you are overthinking this.

Take a GUID and remove the dashes. That's your new "key". store it as a
string in the code.
XOR the SSN with the key characters in order.

That way, it will not be terribly easy to pick out the pattern.

Normally, you'd want to create a Hash. You may also want to consider using
the Crypto API or CAPICOM to create an MD5 hash, and then take the first 20
characters and XOR them to the second 20 characters to get your value.

That's a pretty hair-brained idea, and I'm NOT certain that the result will
always be unique...

Anyway, good luck,
--- Nick
 
T

Tim Gallivan

Sorry,
Try googling "one-time pad" and you'll see the algorithm for encrypting data
using one. They are supposedly unbreakable - as long as you never re-use the
pad.

Say you have this 8 char string to encrypt:

FrTonMqA

and the first 8 chars of your pad are:

12345678

and when you encrypt your string, you have:

$rT*vbBQ

then you can stick your pad inside your encrypted string (or cyphertext).
The rule will be insert pad(1) and (2) after char(2), pad(4) and (3) after
char(3), pad(8) after char(4), pad(7) and (6) after (5), and pad(5) after
char(7), so you get:

$r12T43*8v76bB5Q

On the receiving end, you know the rule, so you strip out and reassemble the
pad, then apply it in a reverse algorithm and you get the original text.
 
C

Colin Young

The problem with this solution is that you are including the pad in the
message. This is security through obscurity. It only has a chance of working
until an attacker discovers your method, at which point it's completely
useless. i unfortunately don't know enough about attacking cryptographic
systems to know if one could easily break it without knowledge of the
algorithm.

The difficulty with one-time pads is getting the pad to the receiving party
without it being intercepted.

Colin
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top