一对一整数映射函数

时间:2011-09-02 12:48:04

标签: math hash cryptography

我们正在使用MySQL并开发一个应用程序,我们希望ID序列不被公开显示...... ID几乎不是绝密的,如果有人确实能够解码它们就没有重大问题。

所以,哈希当然是显而易见的解决方案,我们目前正在使用MD5 ... 32位整数进入,我们将MD5修剪为64位然后存储。但是,我们不知道当你这样修剪时碰撞的可能性(特别是因为所有数字都来自自动增量或当前时间)。我们目前检查是否存在冲突,但由于我们可能会同时插入100,000行,因此性能非常糟糕(无法批量插入)。

但最后,我们真的不需要哈希提供的安全性,它们会占用不必要的空间,还需要一个额外的索引...所以,有没有任何简单和足够好的功能/算法保证对于任何数字的一对一映射,没有明显的序列号视觉模式?

编辑:我使用的PHP默认情况下不支持整数运算,但在环顾四周后我发现它可以通过按位运算符进行廉价复制。可以在此处找到32位整数乘法的代码:http://pastebin.com/np28xhQF

5 个答案:

答案 0 :(得分:6)

你可以简单地用0xDEADBEEF进行XOR,如果那还不错的话。

或者乘以奇数mod 2 ^ 32。对于逆映射,只需乘以multiplicative inverse

示例:n = 2345678901;乘法逆(mod 2 ^ 32):2313902621 对于映射,只需乘以2345678901(mod 2 ^ 32):

1 - > 2345678901 2 - > 396390506

对于逆映射,乘以2313902621。

答案 1 :(得分:3)

如果要确保1:1映射,则使用加密(即置换),而不是哈希。加密必须是1:1,因为它可以解密。

如果你想要32位数字,那么使用Hasty Pudding Cypher或者只写一个简单的四轮Feistel密码。

这是我之前准备的:

import java.util.Random;

/**
 * IntegerPerm is a reversible keyed permutation of the integers.
 * This class is not cryptographically secure as the F function
 * is too simple and there are not enough rounds.
 *
 * @author Martin Ross
 */
public final class IntegerPerm {
    //////////////////
    // Private Data //
    //////////////////

    /** Non-zero default key, from www.random.org */
    private final static int DEFAULT_KEY = 0x6CFB18E2;

    private final static int LOW_16_MASK = 0xFFFF;
    private final static int HALF_SHIFT = 16;
    private final static int NUM_ROUNDS = 4;

    /** Permutation key */
    private int mKey;

    /** Round key schedule */
    private int[] mRoundKeys = new int[NUM_ROUNDS];

    //////////////////
    // Constructors //
    //////////////////

    public IntegerPerm() { this(DEFAULT_KEY); }

    public IntegerPerm(int key) { setKey(key); }

    ////////////////////
    // Public Methods //
    ////////////////////

    /** Sets a new value for the key and key schedule. */
    public void setKey(int newKey) {
        assert (NUM_ROUNDS == 4) : "NUM_ROUNDS is not 4";
        mKey = newKey;

        mRoundKeys[0] = mKey & LOW_16_MASK;
        mRoundKeys[1] = ~(mKey & LOW_16_MASK);
        mRoundKeys[2] = mKey >>> HALF_SHIFT;
        mRoundKeys[3] = ~(mKey >>> HALF_SHIFT);
    } // end setKey()

    /** Returns the current value of the key. */
    public int getKey() { return mKey; }

    /**
     * Calculates the enciphered (i.e. permuted) value of the given integer
     * under the current key.
     *
     * @param plain the integer to encipher.
     *
     * @return the enciphered (permuted) value.
     */
    public int encipher(int plain) {
        // 1 Split into two halves.
        int rhs = plain & LOW_16_MASK;
        int lhs = plain >>> HALF_SHIFT;

        // 2 Do NUM_ROUNDS simple Feistel rounds.
        for (int i = 0; i < NUM_ROUNDS; ++i) {
            if (i > 0) {
                // Swap lhs <-> rhs
                final int temp = lhs;
                lhs = rhs;
                rhs = temp;
            } // end if
            // Apply Feistel round function F().
            rhs ^= F(lhs, i);
        } // end for

        // 3 Recombine the two halves and return.
        return (lhs << HALF_SHIFT) + (rhs & LOW_16_MASK);
    } // end encipher()

    /**
     * Calculates the deciphered (i.e. inverse permuted) value of the given
     * integer under the current key.
     *
     * @param cypher the integer to decipher.
     *
     * @return the deciphered (inverse permuted) value.
     */
    public int decipher(int cypher) {
        // 1 Split into two halves.
        int rhs = cypher & LOW_16_MASK;
        int lhs = cypher >>> HALF_SHIFT;

        // 2 Do NUM_ROUNDS simple Feistel rounds.
        for (int i = 0; i < NUM_ROUNDS; ++i) {
            if (i > 0) {
                // Swap lhs <-> rhs
                final int temp = lhs;
                lhs = rhs;
                rhs = temp;
            } // end if
            // Apply Feistel round function F().
            rhs ^= F(lhs, NUM_ROUNDS - 1 - i);
        } // end for

        // 4 Recombine the two halves and return.
        return (lhs << HALF_SHIFT) + (rhs & LOW_16_MASK);
    } // end decipher()

    /////////////////////
    // Private Methods //
    /////////////////////

    // The F function for the Feistel rounds.
    private int F(int num, int round) {
        // XOR with round key.
        num ^= mRoundKeys[round];
        // Square, then XOR the high and low parts.
        num *= num;
        return (num >>> HALF_SHIFT) ^ (num & LOW_16_MASK);
    } // end F()

} // end class IntegerPerm

答案 2 :(得分:1)

亨里克在他的第二个建议中说了些什么。但是因为这些值似乎被人们使用(否则你不想让它们随机化)。再迈一步。将序号乘以一个大素数并减少mod N,其中N是2的幂。但是选择N比您可以存储的小2位。接下来,将结果乘以11并使用它。所以我们有:

Hash =((count * large_prime)%536870912)* 11

乘以11可防止大多数数据输入错误 - 如果输入的数字错误,结果将不是11的倍数。如果转换了任何2位数,结果将不是11的倍数。对输入的任何值进行初步检查,在查看数据库之前,检查它是否可被11整除。

答案 3 :(得分:0)

您可以对大素数使用mod运算。

你的号码*大素数1 /大素数2.

素数1应大于秒。秒数应接近2 ^ 32但小于它。比它难以替代。

Prime 1和Prime 2应该是常量。

答案 4 :(得分:0)

对于我们的应用程序,我们使用位洗牌来生成ID。还原回原始ID非常容易。

func (m Meeting) MeetingCode() uint {
    hashed := (m.ID + 10000000) & 0x00FFFFFF
    chunks := [24]uint{}
    for i := 0; i < 24; i++ {
        chunks[i] = hashed >> i & 0x1
    }
    shuffle := [24]uint{14, 1, 15, 21, 0, 6, 5, 10, 4, 3, 20, 22, 2, 23, 8, 13, 19, 9, 18, 12, 7, 11, 16, 17}
    result := uint(0)
    for i := 0; i < 24; i++ {
        result = result | (chunks[shuffle[i]] << i)
    }
    return result
}