敏感信息加密

保密

保密是有成本的,追求越高的安全等级,我们就要付出越多的工作量与算力消耗。

就连国家保密法都会把秘密信息划分为秘密、机密、绝密三级来区别对待,可见即使是信息安全,也应该有所取舍。

1、Hash加密

以摘要代替明文如果密码本身比较复杂,那么一次简单的哈希摘要就至少可以保证,即使在传输过程中有信息泄露,也不会被逆推出原信息;

即使密码在一个系统中泄露了,也不至于威胁到其他系统的使用。但这种处理不能防止弱密码被彩虹表攻击所破解。

2、先加盐值再做哈希

盐值可以替弱密码建立一道防御屏障,在一定程度上可以防御已有的彩虹表攻击。

但它并不能阻止加密结果被监听、窃取后,攻击者直接发送加密结果给服务端进行冒认。

3、将盐值变为动态值能有效防止冒认

如果每次向服务端传输时,密码都掺入了动态的盐值,让每次加密的结果都不一样,那么即使传输给服务端的加密结果被窃取了,攻击者也不能冒用来进行另一次调用。

不过,尽管在双方通讯均可能泄露的前提下,协商出只有通讯双方才知道的保密信息是完全可行的(后面两讲介绍“传输安全层”时会提到),但这样协商出盐值的过程将变得极为复杂,而且每次协商只能保护一次操作,因而也很难阻止攻击者对其他服务的重放攻击

4、加入动态令牌防止重放攻击

我们可以给服务加入动态令牌,这样在网关或其他流量公共位置建立校验逻辑,服务端愿意付出在集群中分发令牌信息等代价的前提下,就可以做到防止重放攻击。

但这种手段的弱点是,依然不能抵御传输过程中被嗅探而泄露信息的问题。

5、启用 HTTPS 来应对因嗅探而导致的信息泄露

启用 HTTPS 可以防御链路上的恶意嗅探,也能在通讯层面解决重放攻击的问题。

但是它依然有因客户端被攻破而产生伪造根证书的风险、因服务端被攻破产生证书泄露被中间人冒认的风险、因CRL更新不及时或者OCSP Soft-fail 产生吊销证书被冒用的风险,以及因 TLS 的版本过低或密码学套件选用不当产生加密强度不足的风险。

进一步提升保密强度的不同手段为了抵御前面提到的这种种风险,我们还要进一步提升保密强度。比如说,银行会使用独立于客户端的存储证书的物理设备(俗称的 U 盾),来避免根证书被客户端中的恶意程序窃取伪造;当大型网站涉及到账号、金钱等操作时,会使用双重验证开辟出一条独立于网络的信息通道(如手机验证码、电子邮件),来显著提高冒认的难度;甚至一些关键企业(如国家电网)或机构(如军事机构),会专门建设遍布全国各地的、与公网物理隔离的专用内部网络,来保障通讯安全。

通过了解以上这些逐步升级的保密措施,你应该能对“更高的安全强度同时也意味着要付出更多的代价”,有更加具体的理解了,并不是任何一个网站、系统、服务都需要无限拔高的安全性。

也许这个时候,你还会好奇另一个问题:安全的强度有尽头吗?存不存在某种绝对安全的保密方式?答案可能会出乎你的意料,确实是有的。信息论之父香农就严格证明了一次性密码(One Time Password)的绝对安全性。

但是使用一次性密码必须有个前提,就是我们已经提前安全地把密码或密码列表传达给了对方。比如说,你给朋友人肉送去一本存储了完全随机密码的密码本,然后每次使用其中一条密码来进行加密通讯,用完一条丢弃一条。

这样理论上可以做到绝对的安全,但显然这种绝对安全对于互联网来说没有任何的可行性。

6、什么程度加密是合适的,可以接受的

        int password = 123456;

        client_hash = MD5(password) // e10adc3949ba59abbe56e057f20f883e
        //MD5Hash 有高效碰撞的问题,加盐
        client_hash = MD5(MD5(password) + salt)  // SALT = $2a$10$o5L.dWYEjZjaejOmN3x4Qu
        //上述可以彩虹表破解
        //使用慢哈希函数来解决暴力破解问题,加密客户端的hash
        client_hash = BCrypt(MD5(password) + salt) // MFfTW3uNI4eqhwDkG7HP9p2mzEUu/r2

        //服务端生产动态盐
        SecureRandom random = new SecureRandom();
        byte server_salt[] = new byte[36];
        random.nextBytes(server_salt);   // tq2pdxrblkbgp8vt8kbdpmzdh1w8bex

        server_hash = SHA256(client_hash + server_salt);  // 55b4b5815c216cf80599990e781cd8974a1e384d49fbde7776d096e1dd436f67
        DB.save(server_hash, server_salt);

        authentication_hash = MFfTW3uNI4eqhwDkG7HP9p2mzEUu/r2
        result = SHA256(authentication_hash + server_salt); // 55b4b5815c216cf80599990e781cd8974a1e384d49fbde7776d096e1dd436f67
        authentication = compare(result, server_hash) // yes

7、加密代码的实现

package com.lf.java.architecture;

import org.mindrot.jbcrypt.BCrypt;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class PasswordHashingUtil {

    // 生成随机盐值
    public static String generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] saltBytes = new byte[16];
        random.nextBytes(saltBytes);
        return bytesToHex(saltBytes);
    }

    // 使用MD5对密码进行散列
    public static String hashWithMD5(String data) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("MD5");
        byte[] hashBytes = digest.digest(data.getBytes());
        return bytesToHex(hashBytes);
    }

    // 使用BCrypt对密码进行散列
    public static String hashPasswordWithBCrypt(String password, String salt) {
        return BCrypt.hashpw(password + salt, BCrypt.gensalt());
    }

    // 使用SHA-256对散列值和盐值进行散列
    public static String hashWithSHA256(String data) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hashBytes = digest.digest(data.getBytes());
        return bytesToHex(hashBytes);
    }

    // 将字节数组转换为十六进制字符串
    public static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            hexString.append(String.format("%02x", b));
        }
        return hexString.toString();
    }

    public static void main(String[] args) throws NoSuchAlgorithmException {
        String password = "user123";
        String clientSalt = " $2a$10$o5L.dWYEjZjaejOmN3x4Qu";

        //使用慢哈希函数来解决彩虹表暴力破解问题,生成客户端的hash
        // 使用BCrypt对密码进行散列(需要客户端使用慢哈希方法生成client,增加暴力破解时间成本)
        String clientHash = hashPasswordWithBCrypt(hashWithMD5(password), clientSalt);

        // MD5Hash 有高效碰撞的问题,使用SHA-256对散列值和盐值进行散列
        try {
            //生成服务端动态盐
            String salt = generateSalt();
            String serverHash = hashWithSHA256(clientHash + salt);
            System.out.println("Client Hash (BCrypt): " + clientHash);
            System.out.println("Server Hash (SHA-256): " + serverHash);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(架构随笔,网络,安全)