如何使用密码和 Java 将 12 位十进制数字加密/解密为其他数字?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/858476/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 14:05:50  来源:igfitidea点击:

How can I encrypt/decrypt 12-digit decimal numbers to other ones, using a password and Java?

javaencryption

提问by Kannan Ekanath

I have already read Using Java to encrypt integersand Encrypting with DES Using a Pass Phrase.

我已经阅读了Using Java to encrypt integersEncrypting 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:

我只需要一个简单的加密器,它可以将 12 位数字转换为具有以下约束的 12 位数字:

  1. The encryption must depend on a password (which will be constant throughout the life time of an application) and nothing else.
  2. The mapping must be 1-1 (No hashing and multiple inputs giving same output and vice versa).
  3. 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).
  4. Numbers starting with 0 is not a valid 12 digit number (also input numbers won't start with 0).
  5. 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.
  6. All inputs will be exactly 12 digits and less than a 12 digit prime number (which means we could use modulo arithmetic).
  1. 加密必须依赖于密码(在应用程序的整个生命周期中都是不变的)而不是其他任何东西。
  2. 映射必须是 1-1(没有散列和多个输入给出相同的输出,反之亦然)。
  3. 不同 VM 之间或 VM 启动时的映射不得更改(例如,当您重新启动 Java 时,该实用程序应该为您提供相同的映射,这意味着它必须完全依赖于提供的密码)。
  4. 以 0 开头的数字不是有效的 12 位数字(输入的数字也不会以 0 开头)。
  5. 密钥/密码不应该是可猜到的。例如,运行具有多个输入的实用程序并分析输出不应让人们猜测密钥/密码/哈希或其他任何东西。
  6. 所有输入都将是 12 位且小于 12 位质数(这意味着我们可以使用模运算)。

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

我遇到了问题

  1. How to convert the 16 bytes into a 12 digit number.
  2. Maintain 1-1 mapping.
  3. Keep the encryption/decryption same across multiple VM invocations.
  1. 如何将 16 个字节转换为 12 位数字。
  2. 维护 1-1 映射。
  3. 在多个 VM 调用中保持加密/解密相同。

**** 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?

我添加了一个答案,它是从标准 Java RSA 密钥对生成逻辑中提取的 40 位 RSA。我仍然需要在边缘情况下工作。我将接受答案并投票赞成“Tadmas”,我认为他可以引导我找到答案。有人能告诉我我的算法是否会变弱/可攻击吗?

回答by Jon Skeet

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].

您将无法在不丢失信息的情况下将 16 个字节转换为 12 位数字。256^16>10^12。(并不是说您甚至有 10^12 个选项,因为您只有范围 [100000000000, 999999999999]。

I doubt that you'll be able to use any traditional encryption libraries, as your requirements are somewhat odd.

我怀疑您是否能够使用任何传统的加密库,因为您的要求有些奇怪。

回答by Tadmas

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. :)

如果严格的 1:1 映射比防止密码分析更重要,那么您可以将密码转换为 12 位数字(通过哈希或其他方式)并简单地添加到您的原始数字 mod 10^12。如果您绝对必须从输出中删除前导零,您可以减去 10^11,进行数学模数 (10^12 - 10^11),然后再次添加 10^11。当然,这是非常不安全的,但它很简单。:)

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.

如果输入范围以小于 (10^12 - 10^11) 的素数为界,您可能可以使用 message ^ password mod prime 来形成一个满足您要求的环,并且更难破解。(这类似于 RSA 的工作方式。)我认为如果您不需要解密它,这可以工作。

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.

我同意 Jon Skeet 的观点:要求严格的 1:1 映射且输出范围不大于输入域,这是大多数加密库不会处理的。

回答by Accipitridae

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.

一种潜在的解决方案可以建立在Feistel 密码上。这种构造允许基于伪随机函数构建伪随机排列。例如,通过将结果截断为 6 位数字,可以从适当的分组密码构造伪随机函数。

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

此构造已在以下论文 M. Luby 和 C. Rackoff 中进行了分析,“如何从伪随机函数构造伪随机排列” SIAM 计算杂志,第 17 卷,第 2 期,第 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.

一个具体的提议是Feistel 有限集加密模式,它已提交给 NIST 以可能包含在即将推出的标准中。该提议还解决了加密不是 2 的幂的范围的问题。

回答by Neil Coffey

If the numbers are for user IDs, this is what I'd do:

如果数字用于用户 ID,这就是我要做的:

(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.

(1)从密码生成AES密钥。如果您相信管理员会使用非常非常强大的密码,只需调用 getBytes() 就可以了。理想情况下,使用标准的“基于密码的加密”技术对字节进行散列,例如几千次,每次添加您最初生成的随机“盐”字节以避免字典攻击。

(2) Encrypt the number in question with that AES key.

(2) 使用该 AES 密钥加密有问题的数字。

(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.

(3) 从生成的加密块中切掉 12 位数字,将其转换为十进制,然后将该数字呈现给用户。(为此,您可以将 BigInteger 包裹在字节周围,对其调用 toString(),然后提取位置 4 和 16 之间的字节。)实验上,看起来您不应该从最右端。

[Update: I thinkthis 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.]

[更新:我认为这可能是因为 BigInteger 字面上从左到右分配它的数字——但我没有检查过——所以最右边的字节中可能有“备用”位,因此可能的数字更少如果包含最后一个字节。]

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.

现在,我听到你哭了,这显然不是 1-1 映射。但除非您将拥有超过数万名用户,否则它真的足够好。对于 12 位数字,您预计在发生冲突之前平均可以加密大约 300,000 个数字。因此,尽管您没有严格的 1-1 映射,但实际上,它几乎是该死的。

(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:

只是为了说服自己假装它是 1-1 映射确实可以,您可以运行一个模拟,重复尝试分配,例如,200,000 个具有随机密钥的用户 ID,并打印出每次运行有多少冲突:

 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.");
        }

回答by Daniel Brückner

I suggest a very simple algorithm.

我建议一个非常简单的算法。

  1. Feed the password into a hash function.
  2. Initialize a random number generator with the hash or something you derived from the hash.
  3. Generate a 12 digit random number.
  4. Add this random number to the input digit by digit modulo 10 to encrypt.
  1. 将密码输入散列函数。
  2. 使用散列或从散列派生的内容初始化随机数生成器。
  3. 生成一个 12 位的随机数。
  4. 将此随机数与输入的数位相加10为模数进行加密。

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 Padwas 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.

解密减去模 10 的随机数。这实际上是One Time Pad 的一种形式。由于对此答案的评论,我意识到参考One Time Pad是一个糟糕的选择。一个更好的参考是Polyalphabetic cipher- 虽然 One Time Pad 使用了 polyalphabetic 替换,但它的主要特征是不使用任何密钥位两次。

   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 XORinstead 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 XORagain, the first digit will turn into zero and you know that the first digit was not enrcypted.

剩下的一个问题是 - 该算法可能会创建前导零。为了解决这个问题,可以用XOR加法和减法代替。只需将数字转换为XOR. 如果第一个数字变成零,不要加密第一个数字。当你XOR再次解密时,第一个数字会变成零,你知道第一个数字没有被加密。

UPDATE

更新

A simple XORis not the solution because it will produce to large numbers - 2 XOR 9 = 11for example. Going to rethinks this...

简单XOR的不是解决方案,因为它会产生大量 -2 XOR 9 = 11例如。打算重新考虑这个...

UPDATE

更新

The nice propoerties of XORare 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 XORdoesn't do.
But maybe we can build a custom function with all required properties. So we create an array FUNCwith 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.)

的好的属性XORXOR(a, b) = XOR(b, a)XOR(XOR(a, b), b) = a。这使得加密和解密相同,并允许检测未加密的前导数字。但进一步要求我们的函数只返回 0 到 9 范围内的值,而这XOR不做任何事情。
但也许我们可以构建一个具有所有必需属性的自定义函数。因此,我们创建了一个FUNC10 列 10 行的数组,并将其用作我们函数的查找表。但在什么值?我实际上不知道 - 我什至不确定这是可能的。但是如果我们从 0 到 9 的范围内选择三个数字,我们必须输入以下六个条目。(它是一个对称矩阵。)

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.

所以也许可以通过重复选择随机数并在没有冲突的情况下填充六个条目来创建这样的函数。也许不是。如果有人找到解决方案,我想看看表格。

回答by Kannan Ekanath

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).

我认为 Tadmas 在下面给出的答案非常有帮助,我希望你们在下面攻击/欺负我的实现。正如 Tadmas 指出的,我所有的数字都是 40 位(12 位数字是 10^12,大约是 2^40)。

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,

我复制了 sun.security.rsa.RSAKeyPairGenerator(链接)并为 40 位 RSA 算法创建了我自己的生成器。标准的需要 512-1024 位,所以我删除了它周围的输入检查。一旦我创建了一个合适的 n、e、d 值(根据 alog,e 似乎是 65537)。以下代码运行良好,

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

缺点

  1. 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...
  2. 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 !
  1. 编码可能并不总是 12 位(有时它可能以 0 开头,这意味着只有 11 位)。我想总是在前面填充 0 个零并在开头添加一些 CHECKSUM 数字,这可能会缓解这个问题。所以 13 位数字总是...
  2. 40 位 RSA 比 512 位弱(不仅仅是 512/40 倍,而是指数倍数)。各位专家能否指出有关 40 位 RSA 与 512 位 RSA 相比有多安全的链接(我可以在 wiki 中看到一些内容,但无法具体确认攻击的可能性)?任何有关破解 RSA 所需的概率/尝试次数的链接(维基?)作为 N 的函数,其中 n 是使用的位数,这将很棒!

回答by David R Tribble

I would use a streamcipher. Nbytes go in, Nbytes come out.

我会使用密码N个字节输入,N个字节输出。

回答by Jason Smith

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

该线程已有 4 年历史,但对于那些在 Google 中找到该线程的人:查看 Format Preserving Ciphers:http: //en.wikipedia.org/wiki/Format-preserving_encryption

回答by izb

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.

将 12 位数字视为两个 6 位数字。然后使用密码的散列作为随机数生成器的种子,该生成器将 999,990 个连续值的表打乱。然后使用两个 6 位数字在表中查找条目。两个结果的串联是基于密码的 1:1 映射的 12 位“加密”输入。

Let's do an example with 4-digits instead of 12...

让我们用 4 位数字而不是 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...

并用 37592 作为随机种子洗牌......

b = [45, 15, 56, 49, .. 33, 88]

Now split the input: 48, 52 and look up those indices in the shuffled array, say...

现在拆分输入:48, 52 并在混洗数组中查找这些索引,例如...

b[48] => 23
b[52] => 96

So our encrypted version of 4852 is 2396

所以我们4852的加密版本是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.

这确实不是一个强有力的解决方案,但您问题中的限制不会导致一个强有力的解决方案。您可能需要稍微放宽这些限制。

回答by Daniel Brückner

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^12is 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 Blowfishsupporting block lengths from 32 to 448 bits in steps of 8 bits - so 40 bits is supported.

重新思考这个问题,我想出了以下内容。基本上你需要一个对称密码来获得一对一的映射。注意到这10^12几乎是2^40( 2^39.863) 将您的 12 位数字转换为 40 位整数并将该数字输入到块长度为 40 位的块密码中似乎很自然。一个不错的选择可能是Blowfish,它以 8 位的步长支持从 32 到 448 位的块长度 - 因此支持 40 位。



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.

正如 Accipitridae 所指出的,Blowfish 的密钥大小可变,但块大小固定,因此没有选择。通过网络进行更多搜索似乎表明,很少或没有块大小为 40 位或更少的密码使这个想法无效。留下答案的剩余部分——也许可以找到合适的密码。



The remaining problem is that the Blowfishmight return a number up to 1,099,511,627,775with 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 transformto 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^40while 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 Ciphersdescribes how to construct a block cipher that can deal with every block size from nup to 2ngiven 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.

剩下的问题是河豚可能会返回一个最多1,099,511,627,77513 位的数字,并且返回的数字可能包含前导零,但我相信这可以在第二步中解决。我的第一个想法是将Burrows-Wheeler 变换之类的东西应用到数字的字符串表示中,以便在数字的第 13 位数字前面至少得到一个零,然后修改所有剩余的数字(例如 0 <-> 9, 1 <-> 8, 2 <-> 7, ...) 将其他前导零转换为其他数字。
几分钟后,我意识到这将不起作用 - 输入有 size2^40而输出只有 size 2^39.863。一种解决方案是使用 39 位分组密码并将输入限制为最多549,755,813,887. 我不知道是否有可以处理 39 位块长度的密码,但是这篇关于弹性块密码的论文描述了如何构建一个块密码,该密码可以处理从n最大到2n给定块密码的每个块大小可以处理块大小n因此,可以从 32 位 Blowfish 构建 39 位分组密码。