学堂 学堂 学堂公众号手机端

在程序中采用了静态变量的方式实例化了MessageDigest

lewis 4年前 (2021-04-22) 阅读数 5 #技术

问题描述

在程序中采用了静态变量的方式实例化了MessageDigest

    private static MessageDigest CRYPT = null;

    static {
        try {
            CRYPT = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e) {
            log.warn("Failed to get sha-1 message digest.");
        }
    }

然后采用SHA1进行加密


    private String encryptSHA1(String key) {
        String sha1 = "";
        CRYPT.reset();
        //1
        CRYPT.update((key + SALT).getBytes(StandardCharsets.UTF_8));
        //2         sha1 = byteToHex(CRYPT.digest());
        return sha1; 
    }

SALT目前是固定值,当调用encryptSHA1时,偶尔会返回错误的加密值。

实验验证

首先,SHA1的加密方式是每次加密结果都一致。所以猜测是并发引起的。下面实验验证一下。

实验一:

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            System.out.println(service.encryptSHA1("12345678"));
            System.out.println(service.encryptSHA1("87654321"));
            Thread.sleep(100);
        }
    }

结果没有问题。

实验二:

    public static void main(String[] args) {
        Thread[] ts = new Thread[100];
        for (int i = 0;i<100;i++) {
            Thread t = new Thread(String.valueOf(new Random().nextLong())) {
                public void run() {
                    service.encryptSHA1(Thread.currentThread().getName());
                }
            };
            ts[i] = t;
        }
        for (int i = 0;i<100;i++) {
            Thread t = ts[i];
            t.start();
        }
    }

这个结果出问题了,当并发的时候,加密的结果偶尔是上一个加密的结果。

实验三:

    private String encryptSHA1(String key) {
        String sha1 = byteToHex(DigestUtils.sha1((key + SALT).getBytes(StandardCharsets.UTF_8)));
        return sha1;
    }

再次使用实验二中的main方法验证,结果正常。

分析问题

    private String encryptSHA1(String key) {
        String sha1 = "";
        CRYPT.reset();
        //1
        CRYPT.update((key + SALT).getBytes(StandardCharsets.UTF_8));
        //2
        sha1 = byteToHex(CRYPT.digest());
        return sha1;
    }

由于CRYPT是静态变量,只初始化一次,所以多个线程使用的是同一个对象,当并发比较大时,会出现A线程执行完//1还没有执行//2时,B线程执行完//1,此时A线程执行//2,会显示B线程的结果。

而DigestUtils实际使用的是每次调用 crypt = MessageDigest.getInstance(“SHA-1”); 验证每次生成的crypt都是新的对象。

因此我们也可以自己实现DigestUtils的sha1方法。

版权声明

本文仅代表作者观点,不代表博信信息网立场。

热门