【问题标题】:Implementing MD5, append bit 1 and 0s实现 MD5,附加位 1 和 0
【发布时间】:2011-08-14 10:31:04
【问题描述】:

所以我不确定我这样做是否正确。我这里是伪代码:http://en.wikipedia.org/wiki/MD5

上面写着:

//Pre-processing:
append "1" bit to message
append "0" bits until message length in bits ≡ 448 (mod 512)
append length to message

在 java 中,我能否将消息转换为字节数组。然后通过获取字符串长度 * 8 来获取位数。然后执行448 - ((#bits+1) mod 512) 之类的操作来获取要附加到的 0 位数。

然后将该字节数组复制到另一个数组,但用 0 和 1 填充第一个字节。

例子:

字符串为 746 位 然后我会做448 - ((746+1) mod 512) = 213

所以我需要用 213 个“0”位和 1 个“1”位填充字符串。

那么我的数组会是这样的

byteArr[0] = 0x00
byteArr[1] = 0x00
...
byteArr[27] = 000001(Rest of message bits)
byteArr[n] = Rest of the bytes from message

我怎样才能知道 1 的去向?基本上,如果我附加 1 位,我如何才能知道它是否会创建一个短路。

有没有更简单的方法或其他方法可以做到这一点?

【问题讨论】:

  • 我不鼓励您使用MD5。我当然会推荐最低限度的SHA256 salted。在我下面的代码中,您可以传递MD5SHA-512SHA-256 等等,但MD5(128 位)不如SHA-1(160 位)安全。 SHA-256(256 位)。您可以使用更安全的TripleDES
  • @Ali 是的,我知道 SHA 和 MD5 的区别以及如何使用它们。我想实现MD5的算法。不过还是谢谢。
  • 这里要实现的关键点(除了 append 意味着到末尾):MD5 和其他哈希函数是基于位定义的,而不是字节。理论上,您可以散列一条 13 位长的消息,结果与散列一条以相同方式开始或结束的 16 位长消息不同。但在实践中,几乎所有实现(很可能包括您的实现)都假设输入消息由整个字节组成。

标签: java algorithm md5 implementation


【解决方案1】:

位必须附加到消息的结尾,而不是开头。将消息填充到 448 位 mod 512 后,您必须附加消息的长度(没有填充位)。 但只要这不是练习,您就应该使用 JB Nizet 提到的算法。

【讨论】:

  • 好吧,例如,它会是“消息位”...00000001 或“消息位”1000000000... 以此类推,以 0 开头。我的意思是数组的设置方式,因此这些位将进入数组的末尾。我真的不明白 1 附加到哪里。就像是在最后一个字节之后?
  • 它将是 [messagebits]10000000xxxx,其中 xxxx =“未填充消息的位(非字节)长度为 64 位小端整数”(请参阅​​ wiki)
【解决方案2】:

正如其他人抓到的,“追加”意味着添加到末尾。所以你真正想要的是消息之后的一个 0x80 字节,然后是一堆 0 字节,直到总字节数比 64 的倍数少 8。

【讨论】:

  • 大声笑,这更有意义。当我这么累的时候,我不应该这样做。当您说 0x80 时,这对我来说很有意义。谢谢!
【解决方案3】:

有一种更简单的方法:只需通过 Java SE API 为您使用 MD5 algorithm already implemented

旁注:Java 中的字符串包含字符,而不是字节。不要使用术语“字符串”来指代二进制消息。使用术语字节数组。

【讨论】:

  • 我知道 Java 中已经有一个类,我不想使用它。我想自己实现MD5算法。这是为了我的理解。
  • 啊。好的。除非您编辑您的答案以提及它,否则我将在此处留下我的答案以避免其他类似的答案。
【解决方案4】:

如果您实施加密算法,您应该养成将官方规范作为实施蓝图的好习惯。尽管 Wikipedia 很棒,但我仍然不会依赖它来实现安全关键算法。当然,除了官方规范之外使用它也可以。所以我会从RFC 1321 开始,只会从其他来源获得“灵感”。

RFC 1321 甚至包含 C 中的实现,您也可以检查一下。关于填充步骤,他们说

3.1 步骤 1. 附加填充位

消息被“填充”(扩展),因此其长度(以位为单位)为 等于 448,模 512。也就是说,消息被扩展为 它仅比 512 位长的倍数少 64 位。 始终执行填充,即使消息的长度是 已经与 448 一致,模 512。

填充执行如下:单个“1”位附加到 消息,然后附加“0”位,以便以位为单位的长度 填充的消息与 448 一致,模 512。总而言之,在 至少追加 1 位,最多追加 512 位。

这意味着您必须先附加 1,然后用剩余的零填充,而不是反过来。要填充的其余位(包括“1”)将始终是 8 的倍数,因为您的输入是位长度也是 8 的倍数的字节。所以假设您必须填充 128 位。这意味着 1 加上 127 0 位。总共是 16 (16*8=128) 字节的填充,其中第一个字节的最高位设置为 1,即第一个字节将变为 0x80,其余为 0x00。所以这意味着你可以将这个填充步骤简化为

  • n = 要填充的总字节数 = 余数除以 8
  • 将 0x80 附加到您的消息中
  • 将 n-1 次 0x00 附加到消息中

【讨论】:

    【解决方案5】:

    你说的是给哈希加盐。

    我就是这样做的。

    public static byte[] getSecure8ByteSalt(){
        SecureRandom random = null;
        try {
            random = SecureRandom.getInstance("SHA1PRNG");
            byte [] bSalt =  new byte[8];
            random.nextBytes(bSalt);
            return bSalt;
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage(),e);
        }
        return new byte[]{
             (byte)0xA9, (byte)0x9B, (byte)0xC8, (byte)0x32,
             (byte)0x56, (byte)0x34, (byte)0xE3, (byte)0x03
         };
    }
    

    调用salt的方法称为hash:

    private void hash(String passwd, int hashType){
        byte[] bSalt = new byte[8];
        try {
            if(this.salt == null){
                bSalt = getSecure8ByteSalt();
            }
            else{ 
                bSalt = base64ToByte(salt);
            }
        } catch (IOException e1) {
            log.error(e1.getMessage(),e1);
            return;
        }
    
    
        byte[] bDigest=null;
        try {
            bDigest = getHash(ITERATION_NUMBER,passwd,bSalt,hashType);
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage(),e);
        }
    
        String sDigest = byteToBase64(bDigest);
        if(this.salt == null)
            setSalt(byteToBase64(bSalt));
    setPasswordHash(sDigest);
    }
    

    字节转base 64方法:

    public static byte[] base64ToByte(String data) throws IOException {
        BASE64Decoder decoder = new BASE64Decoder();
        return decoder.decodeBuffer(data);
    }
    
    public static String byteToBase64(byte[] data){
           BASE64Encoder endecoder = new BASE64Encoder();
           return endecoder.encode(data);
    }
    

    getHash 方法:

        private byte[] getHash(int iterationNb, String password, byte[] salt, int hashType) throws NoSuchAlgorithmException {
           MessageDigest digest = MessageDigest.getInstance(HASH_TYPE[hashType]);
           digest.reset();
           digest.update(salt);
           byte[] input = null;
            try {
                input = digest.digest(password.getBytes("UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error(e.getMessage(),e);
            }
           for (int i = 0; i < iterationNb; i++) {
               digest.reset();
               input = digest.digest(input);
           }
           return input;
       }
    

    【讨论】:

    • 这不是加盐哈希。需要填充以使输入的最后一个块具有正确的大小。加盐哈希是不同的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-09
    • 1970-01-01
    • 1970-01-01
    • 2021-01-25
    • 1970-01-01
    相关资源
    最近更新 更多