加解密篇

您所在的位置:网站首页 应用加密的密码是什么密码 加解密篇

加解密篇

2024-07-13 00:31:04| 来源: 网络整理| 查看: 265

这篇是加解密的最后一篇,来聊聊加密加盐。翻看最近的区块链钱包项目,发现 web3j 的源码中对数据做了加盐处理,正好分析一下它是如何进行加盐处理的。

 

目录:

什么是加盐加盐的原理和流程加盐 demoweb3j 的加盐处理

 

 

1. 什么是加盐

盐 (Salt) 在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为 "加盐"。密码不能以明文形式保存到数据库中,否则数据泄露密码就会被知道。而一般的加密方式由于加密规则固定,很容易被破解,安全系数不高。密码加盐的加密方式,能很好的解决这一点。

密码加盐里包含随机值和加密方式。随机值是随机产生的,并且以随机的方式混在原始密码里面,然后按照加密方式生成一串字符串保存在服务器。换言之,这个是单向的,电脑也不知道客户的原始密码,即使知道加密方式,反向推出的加密前的字符串也是真正密码与随机值混合后的结果,从而无法解析用户的真正密码。那么是如何验证密码的呢?当你再次输入密码,会以相同的加盐方式生成字符串,如果和之前的一致,则通过。而其它用户无法获得这种加密方式:即生成哪些随机数,以什么方式混入进去,自然就很安全。

 

 

2. 加盐的原理和流程

第一代密码:

username  | psw

存的明文密码,一旦数据库泄漏,密码一览无余。

 

第二代密码:

username | pwdDigest

不存明文密码,将密码进行摘要再存储 (MD算法,Hash算法)。由于摘要算法是不可逆的  (CRC32 除外),所以即使拿到了数据库,也破解不了密码。但是如今 MD5 已经能够被破解了,不再足够安全。即使破解不了,黑客拿到数据库后,用常用的密码数据集进行消息摘要做匹配,也可以拿到用户的密码。

 

第三代密码:

username | salt | pwdDigest

多了一个字段  - salt。用户在输入密码注册后,随机生成一个 salt,一定要是随机生成的。然后按照一定的混淆规则,将 salt 撒入到明文密码中,最后将加盐后的密码做消息摘要,将 salt 和密码的消息摘要传给服务器。服务器如何匹配用户密码是否正确呢?用户用同样的方式对原始密码加盐再生成摘要,发送给服务器,服务器匹配摘要。因为盐是随机生成的,不用每次都生成盐,盐可以存在本地或者服务器,加盐的规则只要不泄漏出去就行,这样的处理比简单的对密码摘要要安全很多。

这时黑客还能破解吗?

由于盐是随机生成的,每个用户的盐都不相同 (也可能会碰撞),而且对密码加盐的规则只有用户端知道。所以黑客拿到数据库,虽然能看到盐和密码摘要,但是不知道如何将盐混入常用密码集,所以即使知道摘要算法,匹配密码的难度也是非常大的。

 

 

3. 加盐 demo

public class TestSalt { private static char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; public static void main(String[] args) { String psw = "123456"; System.out.println("MD5加密" + "\n" + "明文:" + psw + "\n" + "无盐密文:" + MD5WithoutSalt(psw)); System.out.println("带盐密文:" + MD5WithSalt(psw)); } private static String MD5WithoutSalt(String data) { try { MessageDigest md = MessageDigest.getInstance("MD5"); return byte2HexStr(md.digest(data.getBytes())); } catch (Exception e) { e.printStackTrace(); return e.toString(); } } private static String MD5WithSalt(String data) { try { MessageDigest md = MessageDigest.getInstance("MD5"); String salt = salt(); // 这边是简单的拼合,可以用其他复杂的方式混入 String inputWithSalt = data + salt; String hashResult = byte2HexStr(md.digest(inputWithSalt.getBytes())); System.out.println("加盐密文:" + hashResult); char[] cs = new char[48]; // 输出带盐,存储盐到hash值中; 每两个hash字符中间插入一个盐字符 for (int i = 0; i < 48; i += 3) { cs[i] = hashResult.charAt(i / 3 * 2); cs[i + 1] = salt.charAt(i / 3); cs[i + 2] = hashResult.charAt(i / 3 * 2 + 1); } return new String(cs); } catch (Exception e) { e.printStackTrace(); return e.toString(); } } public static String salt() { Random random = new Random(); StringBuilder sb = new StringBuilder(16); for (int i = 0; i < sb.capacity(); i++) { sb.append(hex[random.nextInt(16)]); } return sb.toString(); } private static String byte2HexStr(byte[] bytes) { int len = bytes.length; StringBuilder result = new StringBuilder(); for (int i = 0; i < len; i++) { byte byte0 = bytes[i]; result.append(hex[byte0 >>> 4 & 0xf]); result.append(hex[byte0 & 0xf]); } return result.toString(); } }

执行输出:

MD5加密 明文:123456 无盐密文:E10ADC3949BA59ABBE56E057F20F883E 加盐密文:1AA9D0E1386C07EE28CE4EFC8A04756A 带盐密文:15AAC9D40E2135863C047E7E208C4E41EF6C88A0247D562A

 

 

4. web3j 的加盐处理

调用 web3j 创建轻量级钱包,password 是钱包密码,ecKeyPair 是一个密钥,包含公钥和私钥:

WalletFile walletFile = Wallet.createLight(password, ecKeyPair);

看看 createLight()的实现:

public static WalletFile createLight(String password, ECKeyPair ecKeyPair) throws CipherException { return create(password, ecKeyPair, 4096, 6); } public static WalletFile create(String password, ECKeyPair ecKeyPair, int n, int p) throws CipherException { byte[] salt = generateRandomBytes(32); byte[] derivedKey = generateDerivedScryptKey(password.getBytes(Charset.forName("UTF-8")), salt, n, 8, p, 32); byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); byte[] iv = generateRandomBytes(16); byte[] privateKeyBytes = Numeric.toBytesPadded(ecKeyPair.getPrivateKey(), 32); byte[] cipherText = performCipherOperation(1, iv, encryptKey, privateKeyBytes); byte[] mac = generateMac(derivedKey, cipherText); return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p); } private static WalletFile createWalletFile(ECKeyPair ecKeyPair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac, int n, int p) { WalletFile walletFile = new WalletFile(); walletFile.setAddress(Keys.getAddress(ecKeyPair)); Crypto crypto = new Crypto(); crypto.setCipher("aes-128-ctr"); crypto.setCiphertext(Numeric.toHexStringNoPrefix(cipherText)); walletFile.setCrypto(crypto); CipherParams cipherParams = new CipherParams(); cipherParams.setIv(Numeric.toHexStringNoPrefix(iv)); crypto.setCipherparams(cipherParams); crypto.setKdf("scrypt"); ScryptKdfParams kdfParams = new ScryptKdfParams(); kdfParams.setDklen(32); kdfParams.setN(n); kdfParams.setP(p); kdfParams.setR(8); kdfParams.setSalt(Numeric.toHexStringNoPrefix(salt)); crypto.setKdfparams(kdfParams); crypto.setMac(Numeric.toHexStringNoPrefix(mac)); walletFile.setCrypto(crypto); walletFile.setId(UUID.randomUUID().toString()); walletFile.setVersion(3); return walletFile; }

只有在创建的时候才会生成 salt,然后存放在本地。salt 的生成规则如下:

static byte[] generateRandomBytes(int size) { byte[] bytes = new byte[size]; SecureRandomUtils.secureRandom().nextBytes(bytes); return bytes; }

调用安全随机工具随机生成的一个长度为32的字节数组,然后将这个字节数组拼接 '0x' 组合成16进制的字符串。

 

钱包生成后,当用户想要进行转账,输入密码后,要拿到钱包的私钥,这时对 salt 是如何处理的呢?

public static ECKeyPair decrypt(String password, WalletFile walletFile) throws CipherException

传入密码和钱包文件对钱包进行解密,解密的代码如下:

public static ECKeyPair decrypt(String password, WalletFile walletFile) throws CipherException { validate(walletFile); Crypto crypto = walletFile.getCrypto(); byte[] mac = Numeric.hexStringToByteArray(crypto.getMac()); byte[] iv = Numeric.hexStringToByteArray(crypto.getCipherparams().getIv()); byte[] cipherText = Numeric.hexStringToByteArray(crypto.getCiphertext()); KdfParams kdfParams = crypto.getKdfparams(); byte[] derivedKey; int c; if (kdfParams instanceof ScryptKdfParams) { ScryptKdfParams scryptKdfParams = (ScryptKdfParams)crypto.getKdfparams(); c = scryptKdfParams.getDklen(); int n = scryptKdfParams.getN(); int p = scryptKdfParams.getP(); int r = scryptKdfParams.getR(); byte[] salt = Numeric.hexStringToByteArray(scryptKdfParams.getSalt()); derivedKey = generateDerivedScryptKey(password.getBytes(Charset.forName("UTF-8")), salt, n, r, p, c); } else { if (!(kdfParams instanceof Aes128CtrKdfParams)) { throw new CipherException("Unable to deserialize params: " + crypto.getKdf()); } Aes128CtrKdfParams aes128CtrKdfParams = (Aes128CtrKdfParams)crypto.getKdfparams(); c = aes128CtrKdfParams.getC(); String prf = aes128CtrKdfParams.getPrf(); byte[] salt = Numeric.hexStringToByteArray(aes128CtrKdfParams.getSalt()); derivedKey = generateAes128CtrDerivedKey(password.getBytes(Charset.forName("UTF-8")), salt, c, prf); } byte[] derivedMac = generateMac(derivedKey, cipherText); if (!Arrays.equals(derivedMac, mac)) { throw new CipherException("Invalid password provided"); } else { byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); byte[] privateKey = performCipherOperation(2, iv, encryptKey, cipherText); return ECKeyPair.create(privateKey); } }

先通过钱包文件获取它的 Crypto,然后通过 Crypto 获取 KdfParams,前面创建钱包时,salt  是存放在 KdfParams 中的。通过KdfParams 就可以获取到这个钱包的 salt 了,然后用用户输入的密码,salt 和一些其他的参数生成最终原始数据的密钥 (derivedKey),相当于上个例子中的 pswDigest。最后通过 derivedKey 和加密的密文还原出 ECKeyPair (公钥和私钥对)。

 

好了,加解密篇就讲到这了,下一个系列是设计模式篇。



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭