使用 MessageDigest 对字符串加密

  今天在阅读代码的时候看到一段很常见的对用户密码进行加密的代码。然鹅,知其然不知其所以然。确定对其解读一番。

public static final String ALGORITHM = "SHA-256";

public static String encrypt(String orignal, String salt) {
    orignal = orignal + salt;
    MessageDigest md = null;
    try {
        md = MessageDigest.getInstance(ALGORITHM);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    if (null != md) {
        byte[] origBytes = orignal.getBytes();
        md.update(origBytes);
        byte[] digestRes = md.digest();
        String digestStr = getDigestStr(digestRes);
        return digestStr;
    }

    return null;
}

private static String getDigestStr(byte[] origBytes) {
    String tempStr = null;
    StringBuilder stb = new StringBuilder();
    for (int i = 0; i < origBytes.length; i++) {
        tempStr = Integer.toHexString(origBytes[i] & 0xff);
        //tempStr = String.valueOf(origBytes[i] & 0xff);
        if (tempStr.length() == 1) {
            stb.append("0");
        }
        stb.append(tempStr);

    }
    return stb.toString();
}

public static void main(String[] args) {
    String str = "123456";
    String rs = encrypt(str, "");
    System.out.println(rs);
}

这是很简单的一段加密代码,百度上到处都是。稍微解读一下,encrypt 方法接收2个参数,orignal 是需要加密的原始字符串,salt 是盐(为了加强密码的强度传入的随机字符串,这里可以忽略)。调用 MessageDigest 类 getInstance 方法获取算法(这里是SHA-256),然后调用相关的 update digest 方法 getDigestStr 对 digest 的返回结果进行一番操作,最终生成加密之后的字符串。

看到 getDigestStr 方法内有一段代码

Integer.toHexString(origBytes[i] & 0xff);

不明觉厉,脑子里充满疑问。

1.0xff 什么意思

基本功不扎实,一顿百度。以下是结果。

0x 开头表示16进制,即16进制下面的 ff。f 对应10进制下 15, 对应2进制下 1111。

所以 ff 10进制下是 255, 2进制下是 1111 1111。

2.origBytes[i] & 0xff 是什么结果

origBytes[i] 的类型为 byte, 结果就是 byte 转换成2进制和 1111 1111 相与。

按位与(&)操作的规则是2个bit都为1则结果为1,否则为0。 如 1010 & 1111 = 1010

那 5 & 0xff 是什么结果?

5 在二进制下为 0000 0101, 00000101 和 11111111 相与, 所以是 0000 0101 & 1111 1111 = 0000 0101。结果还是5。

那 25 & 0xff 是什么结果?

25 在二进制下为 11001, 11001 和 11111111 相与,所以是 0001 1001 & 1111 1111 = 0001 1001。结果还是 25。

可以发现这2个数和 0xff 相与之后结果都是自己,那这么做的意义何在?

5 & 0xff 是 5, 那 -5 & 0xff 呢?

5 在二进制下是 101,那 -5 在二进制下是什么呢?-101?

这里就要穿插一点计算机知识。

正数在计算机中是已原码的形式存在,负数在计算机中是已补码的形式存在。补码=反码+1。

举个例子:

5 的原码是 0101, 反码是原码逐位取反即 1010, 补码为反码加一 1010 + 1 = 1011。所以 5 的补码为 1011,即 -5 在计算机内部是以 1011 的形式存在。

了解完补码之后就可以知道 -5 & 0xff 的结果,假设-5是一个int类型的数,int类型占用4个byte(32bit)

所以先获得5的原码

0000 0000 0000 0000 0000 0000 0000 0101

再获得5的反码

1111 1111 1111 1111 1111 1111 1111 1010

再获得反码的补码

1111 1111 1111 1111 1111 1111 1111 1011

再和 0xff 相与

0000 0000 0000 0000 0000 0000 1111 1111 (这是 0xff 的32位表示)

结果是

0000 0000 0000 0000 0000 0000 1111 1011

转换为10进制是 251。

所以 -5 & 0xff 的结果转换成10进制是 251,并不是-5。

其实观察上面的计算过程可以发现和 0xff 相与相当于取了低8位的bit, 把前面的24位都至0。

3.为什么origBytes[i]要 & 0xff? 为什么是 & 0xff 而不是 & 0xffff 或者是其他的

origBytes[i] 的类型是一个 byte 它占8个bit

origBytes[i] & 0xff 的时候 origBytes[i] 被转成 int 类型,int类型占4个byte(32个bit)

假设现在 origBytes[i] = -5;

origBytes[i] 的值是 1111 1011

origBytes[i] & 0xff 的时候转型成 int 类型,变成了一个32bit的数,

origBytes[i] 的值变成 1111 1111 1111 1111 1111 1111 1111 1011 (扩充到32位)

原来是 0xfb,现在变成了 0xfffffffb

所以和 0xff 相与 把前面的24位都至0 保留低8位,防止了因为符号补位造成的错误。这也是为什么是 & 0xff 而不是 & 0xffff,和其他相与都是不正确的。

扩展符号补位:

窄的整型转换成较宽的整型时,如果最初的数值类型是有符号的,那么就执行符号扩展。即如果符号位为1,则扩展1,如果为0,则扩展0。如果是 char,那么不管被提升成什么类型,都执行零扩展。byte 是有符号的。

扩展2:

byte[] digestRes = md.digest();

算法为 MD5 的时候 digestRes 是一个 16 位的数组,最终的结果是 32 位的长度。

算法为 SHA-1 的时候 digestRes 是一个 20 位的数组,最终的结果是 40 位的长度。

算法为 SHA-256 的时候 digestRes 是一个 32 位的数组,最终的结果是 64 位的长度。

算法为 SHA-384 的时候 digestRes 是一个 48 位的数组,最终的结果是 96 位的长度。

算法为 SHA-512 的时候 digestRes 是一个 64 位的数组,最终的结果是 128 位的长度。

猜你喜欢

转载自my.oschina.net/u/232911/blog/1623356