I am trying to understand OpenSSL encryption. The requirement for me is to decrypt the data encrypted using the below code. It is creating an anonymous ID for receiver. I need to get received ID back.
Based on the documentation I understood encryption is happening using EVP_EncryptFinal. But what is PKCS5_PBKDF2_HMAC_SHA1 doing ?
The code is using KEY and IV.
int create_anon_id(uint32_t recvr_id, uint32_t smartcard_id, const char *hw_id, unsigned char *anon_id)
{
EVP_CIPHER_CTX ctx;
unsigned char ibuf[sizeof(recvr_id) + sizeof(smartcard_id)] = {};
unsigned char obuf[sizeof(ibuf) * 2] = {};
int olen = sizeof(obuf);
/* Convert to big endian. */
recvr_id = bswap32(recvr_id);
smartcard_id = bswap32(smartcard_id);
hw_id = bswap32(hw_id);
/* Fill input buffer with recvr_id | smartcard_id */
memcpy(ibuf, &recvr_id, sizeof(recvr_id));
memcpy(ibuf + sizeof(recvr_id), &smartcard_id, sizeof(smartcard_id));
if (EVP_EncryptInit(&ctx, EVP_des_ede3_cbc(), KEY, IV) == 1)
{
if (EVP_EncryptUpdate(&ctx, obuf, &olen, ibuf, sizeof(ibuf)) == 1)
{
olen = sizeof(obuf) - olen;
if (EVP_EncryptFinal(&ctx, obuf + sizeof(obuf) - olen, &olen) == 1)
{
return PKCS5_PBKDF2_HMAC_SHA1((const char *)obuf, olen, (unsigned char *)hw_id, HW_ID_SIZE, ROUNDS, ANON_ID_BIN_SIZE, anon_id);
}
}
}
}
That code is quite poor, although by bad luck some of the errors are masked by using an input which is exactly one triple-DES data block (64 bits or 8 bytes). If coded correctly the encryption would be done by both EVP_EncryptUpdate and EVP_EncryptFinal; as written the only encryption actually used, of the unpadded block, is done by Update.
But the value returned in anon_id is NOT (JUST) ENCRYPTED. PKCS5_PBKDF2_HMAC[_SHA1] is an implementation of the PBKDF2 algorithm and as used here it computes essentially a slow hash of
recvr_id concatenated with smartcard_id (update: apparently not swapped) then triple-DES encrypted with KEY and IV whatever those are in this code, as the 'passphrase' with
hw_id (update: apparently not swapped, and thus 'accidentally' type-valid) as the salt,
and PRF = HMAC-SHA1 for ROUNDS (update: 10001) iterations
(update) expanded slightly to ANON_ID_BIN_SIZE (22) bytes. (This requires an extra 'pass' of PBKDF2 which actually hurts security; it slows the defender without slowing the attacker.)
PBKDF2 with a decent PRF (HMAC-SHA1 is) is a one-way function and cannot be reversed, as encryption can if you know or guess the key. It is also designed to be costly to bruteforce if the salt and iteration count are chosen well. 32-bits of salt is definitely on the low side, and (update) ROUNDS 10001 is somewhat on the low side but not crazy.
As a better-case example (for you as attacker) if the 32-bit recvr_id and smartcard_id each contain only 16-bits of entropy, and so does hw_id, and ROUNDS was 1000 which was the recommended value when rfc2898 was written at the turn of the century, you'd have to try up to 256 trillion possibilities and a typical desktop computer can do maybe 30 thousand per second in CPU, and with some luck and skill maybe a thousand times that in GPU, so that will take about 3 months.
On the other hand, if the 32-bit inputs are fully entropic and ROUNDS was 1000000, it is impossible with just a computer. If you have a few million dollars to spend on FPGAs (or even better ASICs), and the output of a power plant (or maybe your own hydroelectric dam) to run them, it will still take many millions of years.
Update: with ROUNDS 10001 you are most likely in between these situations, but just where depends strongly on the entropy of the inputs.
And in general this may not produce the desired result anyway. If the output size is less than or even roughly equal to the entropy of the inputs, there will be collisions -- multiple input values (combinations) that produce the desired output, possibly a great many (millions, billions, etc) of such inputs. If you need the original input, rather than merely some input that produces (forges) a known output, you'll need some other method or criteria to select among the possible inputs that bruteforcing finds.
Update: for output 22 bytes with at most 96 bits entropy, this risk is negligible.
Related
I don't code much but, when I'm trying to implement RSA encryption in JAVA my encryption and decryption work for smaller size primes but, when I try to do it for 1536 bit size prime numbers the decryption stops working. I've gone through it but, I don't see what the problem is.
I've already tried to see at what point the bit length starts to be an issue and it seems to be when I set it to 50 is when stops working. I also tend to get an error were my mod inverse method throws an error about e.modinv(lambda)
not being possible. I tried fixing this by adding the do loop but, it seems like this didn't fix the issue.
public BigInteger Random_Prime()
{
SecureRandom random = new SecureRandom();
byte [] randomize = new byte [192];
random.nextBytes(randomize);
BigInteger big = new BigInteger(randomize);
return big.probablePrime(1536,random);
}
public BigInteger lcm(BigInteger p, BigInteger q)
{
long p1 = p.longValue()-1;
long q1 = q.longValue()-1;
BigInteger test1 = p.valueOf(p1);
BigInteger test2 = p.valueOf(q1);
return test1.multiply(test2).divide(test1.gcd(test2));
do {
p = obj1.Random_Prime();
q = obj1.Random_Prime();
lambda = obj1.lcm(p, q);
}
while(lambda.gcd(e).compareTo(ONE)!=0);
BigInteger n = p.multiply(q);
BigInteger m = new BigInteger("75");
BigInteger d = e.modInverse(lambda);
BigInteger c = obj1.Encrypt(n,e,m);
I expect 75 to come back as 75 after going through encryption and decryption.
Your lcm() is wrong.
Java long is only 64 bits including sign, and cannot represent numbers greater than 2^63. (Or equal, but a large prime is never equal to a power of 2.) Thus your lcm computation should work for p,q up to 63 bits, and does for me, but produce totally wrong and useless results for anything larger. Instead use {p,q}.subtract(BigInteger.ONE) for the numbers you multiply-and-divide-by-gcd.
Also, BigInteger.probablePrime(int,Random) is a static (aka classwide) method; you do not need to call it using any instance, much less one with a random value you wasted time computing because it is ignored. For that matter BigInteger.valueOf(long) is also static and ignores any instance used to call it. If you are using any kind of Java development environment more advanced than simply typing javac to a shell or COMMAND, it should (at least optionally) give you a warning about using an instance to call a static method.
Finally, if you aren't aware, using the RSA primitives m^e mod n and c^d mod n directly to encrypt/decrypt data, especially small data, is not secure. You must use a sufficiently large and random padding scheme for this to be secure at all; see wikipedia for a short explanation, and if you want more search on https://crypto.stackexchange.com and maybe https://security.stackexchange.com where this has been asked about and answered many times. And if you decrypt (which you didn't show) simply by doing c.modPow(d,n) that is both inefficient (see wikipedia about CRT) and insecure (see wikipedia about timing attacks, and again crypto.SX and security.SX). And using RSA directly for data is very limiting and inefficient, so in practice people use hybrid encryption -- use a symmetric algorithm (nowadays usually AES) to encrypt the data under a nonce key, and encrypt that nonce key using RSA -- or perhaps better derive it using RSA-KEM (again see wikipedia crypto.SX security.SX).
If you actually want security and not just to play about, use the crypto from the Java library which has been implemented correctly (and reviewed) by competent people, unlike your code.
I generate randomly IV value everytime I encrypt when doing AES/CBC.
private static IvParameterSpec getRandomIvParameterSpec() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}
And I concat IV Value to cipher byte everytime I encrypt.
Is there any secure improvement if I hash (SHA-256) IV value before concat to cipher byte?
SHA-256 is injective. You give it the same input, it will give you the same output. It is not surjective, however. If m1 and m2 both hash to h, you cannot conclude that m1 = m2, even if you know that |m1| = |m2| (both messages are of the same length).
Therefore, applying SHA-256 (or any deterministic function) cannot increase the entropy of your data. At best, it won't decrease it. In other words: If your data is 16 purely random bytes, it won't be “more than purely random” after you hash it. And if your data was not purely random to begin with, then hashing it won't help making it random. You have to use a better entropy source in the first place.
Another problem that you didn't mention is that you currently have 16 random bytes but if you put them into your SHA-256 hash function, you'll get 32 bytes out. Which ones are you going to use? If you only use every second byte – due to injectivity – you won't get all possible bit patterns even if your input was perfectly random and the hash function was flawless. (If you did, then this would – by the pidgin hole principle – mean that the other half of the bytes would always be a function of the bytes you did chose. Only a really crappy hash function, which SHA-256 of course is not, would have such property.) If you try to be clever and combine the bytes in some “smart” way, chances are that you'll make things even worse.
So the short answer is: just don't do it. Generate as many random bytes as you need using the strongest non-deterministic entropy source you have available and use them directly.
I'm trying to solve the problem with the decryption in RSA algorithm.
ArrayList<Integer> m=convertTextToInt(str,e,n,listc);
for (int elem1 : m){
System.out.print( elem1+ " ");
}
Here, I'm passing str which is a string from user it will converted to integer numbers in the convertTextToInt method, a number e and n to calculate the cipher text and array list to store the value after calculate the cipher text with the formula
int c= (int)(Math.pow(Coded,e))%n;
listc.add(c);
then i write this in the main, the problem is that whenever i executed this loop it gives the same result which is 144 !!:
System.out.print("plain text is: ");
for (int element : m){
int plain = (int)(Math.pow(element,d))%n;
listp.add(plain);
System.out.print("listp:"+listp);
}
i tried to change the datatype for the plain to double and it gives me another number but not the right number.
my question is : why the formula of the plain text (description) gives me the same and wrong result every time which is 144 ! :(
Near dupe Storing large numbers for RSA encryption in java
You don't give any clue what numbers you are using, but RSA is only secure if you use numbers of at least about 1000 bits (about 330 decimal digits) which absolutely requires BigInteger in Java. Even this is borderline unsafe; current security standards like those for the WWW from CA/browser forum and for the US government from NIST require 2048 bits.
Even for 'toy' numbers (often used in coursework where no actual security is desired) that fit in Java int (9+ decimal digits) or long (18+ digits), the notional 'raw' decryption computation cd does NOT fit. Math.pow is floating-point so it first returns an imprecise (rounded-off) result, and truncating to int or long turns that into a completely wrong and useless result. You should instead do modular exponentiation in stages with each stage modulo n, as explained in Wikipedia linked from the article on RSA.
If you use BigInteger (as you must for non-toy cases) its modPow method already does this.
Also note 'textbook' RSA -- only exponentiation modulo a large semiprime -- is not actually secure in practice. This is also explained briefly in that Wikipedia article, and https://crypto.stackexchange.com/ has dozens of Q&As on the dangers and limitations of textbook RSA and what to do instead. If you actually want security in Java, at least 'desktop' or 'phone' Java (like Android, but not smartcards and embedded devices), the Java Cryptography Extensions already implement RSA correctly, including the additional things you need such as PKCS1 padding.
I've got a bunch of 48-bit (6 byte) values that I need to encrypt symmetrically. The two requirements are:
The resulting encrypted value needs to also be 48-bits (6 bytes) long. They key itself can be (and would preferably be) much longer to guard again brute force attacks.
The resulting encrypted value needs to be deterministic, i.e. value A using key B will always produce encrypted value C (we encrypt on the fly and show the encrypted data to the user so need to always show the same value)
All block ciphers I've found have used a minimum block size of 64 and appear to be fixed (you can't use an arbitrary block size). Should I be thinking about a stream cipher?
I'm doing this in Java.
Note: I've seen this question and associated answers but wasn't clear on whether the suggestions would satisfy my 2nd requirement.
Consider format preserving encryption.
(Sorry, I originally misread the requirements thinking it was the INPUT data that needed to be 6 bytes.)
I don't think you can do exactly what you want with standard cryptographic algorithms:
the problem with stream ciphers is that standard ones effectively work by generating a stream of pseudorandom bits from the key and then XORing these bits with the plaintext; effectively this means that you should never use the same stream of bits twice (e.g. if you do, then XORing two ciphertexts gives you the same result as XORing the corresponding plaintexts; and in any case with 48 bits, there are only 2^48 possible bitstreams, so you can just test them all by brute force);
the problem with block ciphers is that there's no standard one as far as I'm aware that has a block size of 48 bits.
Now, that doesn't mean that a 48-bit block cipher couldn't be developed-- and indeed I dare say there are some out there-- just that none of the bog-standard ciphers that have undergone years of scrutiny from the cryptographic community have that block size.
So I would suggest options are:
relax the requirement of a 48-bit ciphertext; for example, TripleDES has a 64-bit block size and is "fairly" secure (equivalent to 112 bit security)[*];
in principle, you could implement your own block cipher with whatever block size you require, sticking as close as you can to a standard design, e.g. a Feistel network following some generally recommended design principles-- as a starting point, see Schneier, "Applied Cryptography", pp. 346ff, "Theory of Block Cipher Design".
The obvious problem with the latter option is that, whist standard block ciphers are generally based on common general principles, they adopt particular design decisions that have been subject to considerable scrutiny; yours presumably won't be.
I would also recommend standing back a bit from the problem (or perhaps explaining a bit more what you're trying to do), because it seems to be based on requirements that would normally go against good security practice (having the same plaintext always encrypt to the same ciphertext is something one would normally specifically avoid, for example). So you could have the best designed Feistel cipher in the world, but introduce some other vulnerability in how you're using it.
[*] TripleDES is generally not recommended because AES gives better security more efficiently (you might like to see some comparative timings of block ciphers that I took in Java to see just how bad it is). However, this might not matter in your particular application.
No, just "pad" your data out with some bytes you don't care about (but which are always the same if that's your requirement) so that you reach the size of a block. (If you're using an appropriate padding mode, then this will be done for you.)
I believe this is what you are looking for
http://web.cs.ucdavis.edu/~rogaway/papers/shuffle.html
This algorithm lets you construct PRP (i.e. arbitrary length block cipher) from secure PRF (e.g. sha256, blake2)
Block cipher in CTR mode has the same issue as a stream cipher.
Without a proper MAC (which require more bytes added) it will susceptible to bit flipping.
And without unique IV (which also require more bytes added) it will be just a broken implementation.
You can use a stream cipher only, if you have a unique salt for every encryption (don't even think about re-using the same salt, as that would be trivial to break).
When you have such unique values (e. g. a sequence number that's already associated with your values), you can use e.g. the stream cipher RC4-drop.
When you don't have such unique numbers already, you probably can't use a stream cipher, because you only have 48 bits for your result (so no space left for the salt.)
As for a block cipher with 48 bits - sorry, I don't know such a cipher, either. Maybe what you can do, is combining four 48 bit values into a 192 bit value, resulting in three 64 bit blocks, encode them, and then split them into four 48 bit values again. (I have no idea, if that would be possible in your situation or not?)
If you have a unique counter / sequence number associated with each plaintext value, then you can just use any block cipher in CTR (counter) mode.
To encrypt value V that is sequence number N under key K:
Expand N to the size of a block;
Encrypt N with the block cipher and key K;
Take the first 48 bits of the result and XOR them with V.
Decryption is the same. The most important thing to remember with this method is:
Never, ever use the same key and sequence number to encrypt two different values.
Sequence numbers must be unique. If your sequence restarts, you must use a new key.
There is a 48-bit block 80-bit key cipher designed in 2009 - KATAN48 (the family version of KTANTAN48 has some key scheduling issue. So far, it was not broken, and has quite high security margins, so it has passed the test of time.
Here's a proposed solution that meets both of your requirements.
What if you use a cipher with a 32-bit block size (such as Skip32) and run it twice, as described below.
You have a 48-bit value to encode, for example:
f2800af40110
Step 1:
Split this into a 32-bit value and a 16-bit value using some method. Here we'll just grab the left 4 bytes and the right 2 bytes (but in practice you could use a secret bitmask, see below).
32-bit value: f2800af4
16-bit value: 0110
Step 2:
Encrypt the first one with a secret key K1, using Skip32 and let's say we get:
Encrypted 32-bit value: b0daf2b9
Step 3:
Split this into two 16-bit values (again you could use a secret bitmask, but for this example we'll grab the left/right two bytes).
Value 1: b0da
Value 2: f2b9
Step 4:
Combine value 1 with the the 16-bit value from step 1 to get a new 32-bit value:
b0da0110
Step 5:
Encrypt the resulting 32-bit value with secret key K2, again using Skip32:
Encrypted 32-bit value: 6135d8f4
Step 6:
Combine this 32-bit value with value 2 from step 3 to get a 48-bit encrypted result.
6135d8f4f2b9
The result is both deterministic and reversible. No two inputs will produce the same output.
Note on splitting/combining values
Steps 1 and 3 above will split a value in a predictable way. I'm not sure if this introduces any weakness, but one alternative is to use a bitmask. If we want to split a 48-bit input number into 32-bits and 16-bits, we could come up with a bitmask, essentially a number with 16 1's that can dictate how we split bits of the input number to get two output numbers, as follows:
INPUT : 111100101000000000001010111101000000000100010000
BITMASK: 001010111001110000100000010001100000011001100000
| | ||| ||| | | || || ||
VALUE 1: 1 0 101 000 0 1 10 00 00 => a860
VALUE 2: 11 1 0 00 0000 010101 110 000000 10 10000 => e015c050
Similarly for steps 4 and 6 you could combine two values by interleaving bits based on a bitmask.
If you use separate bitmasks for each step and separate keys for each step you end up with 2 keys and 4 bitmasks, all of which would be needed to encrypt/decrypt values.
Quick note on Skip32 and it's use cases
"The Skip32 has the uncommon properties of being fast, creating very dissimilar encrypted values for consecutive input values, and producing output of the same size as the input. These make this cipher particularly useful for obfuscating series of sequential integers (e.g. auto-incremented database ids)."
- https://docs.rs/skip32/1.0.5/skip32/
Any thoughts about this approach from someone more experienced with cryptography than I am?
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 to a 12 digit number with the following constraints:
The encryption must depend on a password (which will be constant throughout the life time of an application) and nothing else.
The mapping must be 1-1 (No hashing and multiple inputs giving same output and vice versa).
The mapping must not change between different VMs or when VM is started (like when you restart Java, the utility should give you same mappings which means that it must be purely dependent on the password that is supplied).
Numbers starting with 0 is not a valid 12 digit number (also input numbers won't start with 0).
The key/password should never be guessable. For example running the utility with multiple inputs and analysing the outputs should not allow one to guess the key/pwd/hash or whatever.
All inputs will be exactly 12 digits and less than a 12 digit prime number (which means we could use modulo arithmetic).
Having trawled through the literature I have this code with me
public void mytestSimple(long code, String password) throws Exception {
SecretKey key = new SecretKeySpec(password.getBytes(), "DES");
Cipher ecipher = Cipher.getInstance("DES");
ecipher.init(Cipher.ENCRYPT_MODE, key);
System.out.println(ecipher.getOutputSize(8));
byte[] encrypted = ecipher.doFinal(numberToBytes(code));
System.out.println(encrypted + "--" + encrypted.length);
Cipher dcipher = Cipher.getInstance("DES");
dcipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = dcipher.doFinal(encrypted);
System.out.println(bytesToNumber(decrypted) + "--" + decrypted.length);
}
public void testSimple() throws Exception {
mytestSimple(981762654986L, "password");
}
I am running into problems as to
How to convert the 16 bytes into a 12 digit number.
Maintain 1-1 mapping.
Keep the encryption/decryption same across multiple VM invocations.
**** Answer added by me below****
I have added one answer which is a 40bit RSA pulled out of standard Java RSA keypair gen logic. I still have to work on the edge cases. I am going to accept the answer and upvote "Tadmas" who I think kinda lead me to the answer. Can someone tell me if my algorithm is going to be weak/attackable?
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.
If the strict 1:1 mapping is more important than protecting against cryptanalysis, then you can convert the password to a 12-digit number (via hash or otherwise) and simply add to your original number mod 10^12. If you absolutely must remove leading zeros from the output, you can subtract 10^11, do the math mod (10^12 - 10^11), and then add 10^11 back again. Granted, that's extremely insecure, but it's quite simple. :)
If the range of inputs is bounded by a prime less than (10^12 - 10^11), you may be able to use message ^ password mod prime to form a ring that will satisfy your requirements and be a little harder to crack. (This is similar to how RSA works.) I think this could work if you don't need to decrypt it.
I agree with Jon Skeet: requiring a strict 1:1 mapping without the output range being bigger than the input domain is something that most encryption libraries are not going to handle.
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.");
}
I suggest a very simple algorithm.
Feed the password into a hash function.
Initialize a random number generator with the hash or something you derived from the hash.
Generate a 12 digit random number.
Add this random number to the input digit by digit modulo 10 to encrypt.
To decrypt subtract the random number modulo 10. This is actually a form of One Time Pad. Because of the comments on this answer I realized that refering to One Time Pad was a bad choice. A better reference is Polyalphabetic cipher - while One Time Pad uses polyalphabetic substitution its main characteristic is not to use any key bit twice.
Input 1234 1234 1234
Random number 6710 3987 2154
Output 7944 4111 3388
There is one remaining problem with that - the algorithm might create leading zeros. To solve this problem one could use XOR instead of addition and substraction. Just transform the digits with XOR. If the first digit turns into a zero, don't encrypt the first digit. When you decrypt with XOR again, the first digit will turn into zero and you know that the first digit was not enrcypted.
UPDATE
A simple XOR is not the solution because it will produce to large numbers - 2 XOR 9 = 11 for example. Going to rethinks this...
UPDATE
The nice propoerties of XOR are XOR(a, b) = XOR(b, a) and XOR(XOR(a, b), b) = a. This makes encryption and decryption the same and allows to detect the unencrypted leading digit. But it is further required that our function only returns values in the range from 0 to 9 what XOR doesn't do.
But maybe we can build a custom function with all required properties. So we create an array FUNC with 10 columns and 10 rows and use it as a lookup table for our function. What values to but in? I actually don't know - I am not even sure that it is possible. But if we pick three number from the range 0 to 9 we have to make the following six entries. (It is a symmetric matrix.)
FUNC[x,y] = z FUNC[x,z] = y FUNC[y,z] = x
FUNC[y,x] = z FUNC[z,x] = y FUNC[z,y] = x
So maybe it is possible to create such a function by repeatedly choosing random numbers and filling the six entries if there is no conflict. Maybe it is not. I would like to see the table if one finds a solution.
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
The encoded may not always be 12 digits (sometimes it may start with 0 which means only 11 digits). I am thinking always pad 0 zeroes in the front and add some CHECKSUM digit at the start which might alleviate this problem. So a 13 digit always...
A 40 bits RSA is weaker than 512 bit (not just 512/40 times but an exponential factor of times). Can you experts point me to links as to how secure is a 40bit RSA compared to 512 bit RSA (I can see some stuff in wiki but cannot concretely confirm possibility of attacks)? Any links (wiki?) on probabilities/number of attempts required to hack RSA as a function of N where n is the number of bits used will be great !
I would use a stream cipher. N bytes go in, N bytes come out.
This thread is 4 years old, but for those finding the thread in Google: have a look at Format Preserving Ciphers: http://en.wikipedia.org/wiki/Format-preserving_encryption
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.
If I understand your problem correctly you have a problem with the 16 bytes after decrypting. The trick is to use: Cipher.getInstance("DES/ECB/PKCS5Padding");
(just in case code is helpful):
public void mytestSimple(long code, String password) throws Exception
{ SecretKey key = new SecretKeySpec (password.getBytes(),"DES");
Cipher ecipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
byte[] plaintext = new byte[8];
for (int i=0; i<8; i++)
{ plaintext[7-i] = (byte) (code & 0x00FF);
>>>= 8;
}
ecipher.init (Cipher.ENCRYPT_MODE, key);
System.out.println(ecipher.getOutputSize(8));
byte[] encrypted = ecipher.doFinal(plaintext);
System.out.println("--" + encrypted.length);
Cipher dcipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
dcipher.init(Cipher.DECRYPT_MODE, key);
byte[] crypttext = dcipher.doFinal(encrypted);
long decoded = 0;
for (int i=0; i<8; i++)
{ decoded <<= 8;
decoded += crypttext[i] & 0x00FF;
}
System.out.println(decode + "--" + crypttext.length);
}
Rethinking the problem I came up with the following. Basicly you need a symmetric cipher to get a one-to-one mapping. And noting that 10^12 is almost 2^40 (2^39.863) it seems natural to convert your 12 digit number into a 40 bit integer and feed this number into a block cipher with a block length of 40 bits. A good choice might be Blowfish supporting block lengths from 32 to 448 bits in steps of 8 bits - so 40 bits is supported.
UPDATE
As Accipitridae pointed out, Blowfish has variable key size but fixed block size hence it is no option. A bit more searching through the web seems to indicate, that there are little or no ciphers with block sizes of 40 bits or less rendering this idea void. A leave the remaining part of the answer - maybe one can find a suitable cipher.
The remaining problem is that the Blowfish might return a number up to 1,099,511,627,775 with 13 digits and that the returned number might contain leading zeros, but I believe that this can be solved in a second step. My first thought was applying something like a Burrows-Wheeler transform to the string representation of the number in order to get at least one zero to the front of the number eleminating the 13th digit and then modify all remaining digits (for example 0 <-> 9, 1 <-> 8, 2 <-> 7, ...) to turn additional leading zeros into other digits.
After a few minutes I regonized that this will not work - the input has size 2^40 while the output is only of size 2^39.863. A solution would be to use a 39 bits block cipher and restrict the input to numbers up to 549,755,813,887. I don't know if there is a cipher that can deal with a block length of 39 bits, but this paper on Elastic Block Ciphers describes how to construct a block cipher that can deal with every block size from n up to 2n given a block cipher that can handle block size n. In consequence it is possible to construct a 39 bit block cipher from 32 bit Blowfish.
If you're prepared to accept a rather weak solution...
Treat the 12-digit number as two 6-digit numbers. Then use the hash of the password as a seed to a random number generator which shuffles a table of 999,990 consecutive values. Then use the two 6-digit numbers to look up entries in the table. The concatenation of the two results is your 1:1 mapped 12-digit 'encrypted' input based on a password.
Let's do an example with 4-digits instead of 12...
Input: 4852
Password: passw0rd1 => hashcode = 37592
Now take this array..
a = [10, 11, 12, 13, .. 98, 99]
And shuffle it with 37592 as the random seed...
b = [45, 15, 56, 49, .. 33, 88]
Now split the input: 48, 52 and look up those indices in the shuffled array, say...
b[48] => 23
b[52] => 96
So our encrypted version of 4852 is 2396
It really isn't a strong solution but the constraints in your question will not lead to a strong solution. You may need to relax those constraints a bit.