I have already read Using Java to encrypt integers and Encrypting with DES Using a Pass Phrase.
All I need is a simple Encrypter which transforms a 12 digit number t
I would use a stream cipher. N bytes go in, N bytes come out.
You're not going to be able to convert 16 bytes into a 12 digit number without losing information. 256 ^ 16 > 10^12. (Not that you even have 10^12 options, as you've only got the range [100000000000, 999999999999].
I doubt that you'll be able to use any traditional encryption libraries, as your requirements are somewhat odd.
For mathematical reasons, most cyphers will produce "more" bytes (i.e. they will pad the input). So you will have to accept that the code generates 16bytes out of your 12 digit number.
When the string is decoded, you will get the 12 digit number back but during transport, you need 16 bytes.
Me thinks the answer given below by Tadmas was very helpful and I want you guys to hack/bully my implementation below. As Tadmas points out all my numbers are 40 bits (12 digit number is 10^12 which is 2^40 approx).
I copied the sun.security.rsa.RSAKeyPairGenerator (link) and created my own generator for a 40 bit RSA algorithm. The standard one needs between 512-1024 bits so I removed the input check around it. Once I create a suitable n, e, d values (e seems to be 65537 as per the alog). The following code served fine,
public void testSimple() throws NoSuchAlgorithmException {
MyKeyPairGenerator x = new MyKeyPairGenerator();
x.initialize(40, new SecureRandom("password".getBytes()));
MyPublicPrivateKey keypair = x.generateKeyPair();
System.out.println(keypair);
BigInteger message = new BigInteger("167890871234");
BigInteger encoded = message.modPow(keypair.e, keypair.n);
System.out.println(encoded); //gives some encoded value
BigInteger decoded = encoded.modPow(keypair.d, keypair.n);
System.out.println(decoded); //gives back original value
}
Disadvantages
One potential solution could be built on Feistel ciphers. This constructions allows to build a pseudorandom permutation based on a pseudorandom functions. E.g. the pseudorandom functions could be constructed from an appropriate block cipher by truncating the result to a 6 digit numbers.
This construction has been analyzed in the following paper M. Luby and C. Rackoff, "How to construct pseudorandom permutations from pseudorandom functions" SIAM Journal on Computing, Vol.17, No.2, pp.373--386, 1988
A concrete proposal is the Feistel Finite Set Encryption Mode, which has been submitted to NIST for potential inclusion into an upcoming standard. This proposal also addresses the problem of encrypting ranges that are not a power of 2.
If the numbers are for user IDs, this is what I'd do:
(1) Generate an AES key from the password. Just calling getBytes() is sort of OK if you trust the administrator to use a really really really strong password. Ideally, use the standard "password-based encryption" technique of hashing the bytes, say, a few thousand times, each time adding in the random "salt" bytes that you initially generated to avoid dictionary attacks.
(2) Encrypt the number in question with that AES key.
(3) Chop off 12 digits' worth of bits from the resulting encrypted block, convert it to decimal, and present that number to the user. (To do this, you can wrap a BigInteger around the bytes, call toString() on it, and pull off, say, the bytes between position 4 and 16.) Experimentally, it looks like you shouldn't take the digits from the rightmost end.
[Update: I think this is probably because BigInteger literally allocates its numbers from left to rightmost bit-- but I haven't checked-- so there'll potentially be "spare" bits in the very rightmost byte, and hence fewer possible numbers if you include the very last byte.]
Now, I hear you cry, this obviously isn't a 1-1 mapping. But unless you're going to have more than tens of thousands of users, it's really good enough. With a 12-digit number, you'd expect on average to encrypt around 300,000 numbers before getting a collision. So although you don't strictly have a 1-1 mapping, in practice, it's as near as dammit.
(In any case, if your application really has hundreds of thoudands of users and security is crucial, then you'll probably want to invest in some serious consulting over this kind of thing...)
Just to convince yourself that it really is OK to pretend it's a 1-1 mapping, you can run a simulation that repeatedly tries to allocate, say, 200,000 user IDs with random keys, and prints out how many collisions there were on each run:
next_pass :
for (int pass = 0; pass < 100; pass++) {
byte[] key = new byte[16];
(new SecureRandom()).nextBytes(key);
Cipher ciph = Cipher.getInstance("AES");
SecretKeySpec ks = new SecretKeySpec(key, "AES");
ByteBuffer bb = ByteBuffer.allocate(16);
Set<String> already = new HashSet<String>(100000);
int colls = 0;
for (int i = 0; i < 200000; i++) {
bb.putLong(0, i);
ciph.init(Cipher.ENCRYPT_MODE, ks);
byte[] encr = ciph.doFinal(bb.array());
encr[0] &= 0x7f; // make all numbers positive
BigInteger bigint = new BigInteger(encr);
String userNo = bigint.toString();
userNo = userNo.substring(4, 16);
if (!already.add(userNo)) {
System.out.println("Coll after " + i);
continue next_pass;
}
}
System.out.println("No collision.");
}