认证鉴权方案
# 认证鉴权逻辑
# 认证流程

- 用户携带账号、密码、验证码等信息访问权限服务进行认证。
- 认证成功生成jwt token令牌返回给前端,同时将用户拥有的资源权限使用uid作为key保存至缓存。
提示
缓存中保存的用户资源权限来自auth_resource资源表中的method和url字段拼接而成。
auth_resource表结构示例:

缓存的key:value结构如:001 : [ GET/user/info, POST/user/list ]
认证代码示例:
@Slf4j
@Service
public class AuthServiceImpl implements AuthService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private UserInfoClient userInfoClient;
/**
* 用户登录
* @param loginInfo 用户信息
* @param request 请求对象
* @return 认证信息
*/
@Override
public TokenAuthVO login(LoginReq loginInfo, HttpServletRequest request) {
// 验证账号密码
UserInfoDto user = userInfoClient.getUserInfoDtoByAccount(loginInfo.getAccount());
ExceptionUtil.isNull(user, ErrorCodeEnum.USER_ERROR_A0201);
String password = loginInfo.getPassword();
boolean match = BCrypt.checkpw(password, user.getPassword());
ExceptionUtil.assertion(!match, ErrorCodeEnum.USER_ERROR_A0224);
// 生成token视图
TokenAuthVO tokenVO = buildToken(user);
//TODO 获取用户权限放入缓存...
return tokenVO;
}
/**
* 生成token视图
* @param user 用户信息
* @return token视图
*/
private TokenAuthVO buildToken(UserInfo user) {
// 准备token载荷
PayloadUserVO payload = BeanHelper.copyProperties(user, PayloadUserVO.class);
String token = null;
try {
// 读取私钥
String preKeyPath = Objects.requireNonNull(this.getClass().getClassLoader().getResource(FileConstant.PreKey)).getPath();
// 生成token,有效期7天
token = JwtUtils.generateTokenExpireInMinutes(payload, RSAUtil.readPrivateKey(preKeyPath), 60 * 24 * 7);
} catch (Exception e) {
log.error("[token签发异常]:" + e.getMessage(), e);
ExceptionUtil.throwOut(ErrorCodeEnum.USER_ERROR_A0200);
}
TokenAuthVO tokenAuthVO = new TokenAuthVO();
tokenAuthVO.setToken(token);
tokenAuthVO.setUserName(user.getUserName());
return tokenAuthVO;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 鉴权流程
- 用户认证后访问其他业务时都需要将token令牌携带在请求头中,首先经过网关服务处理。
- 在网关服务的令牌过滤器中获取请求头中的token进行解析,将解析出的用户相关数据添加到header中,方便后续过滤器的处理。
- 在网关服务的权限过滤器中获取当前请求用户的资源权限,对当次请求地址进行比对鉴权
网关鉴权代码实例:
@Slf4j
@Order(-1)
@Component
public class AuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 判断地址是合法
String path = request.getURI().getPath();
// 取第一个/后的字符串判断
path = StrUtil.subAfter(path, "/", false);
// 预防越权攻击,如/..auth/
if (path.contains(".")) {
log.error("[鉴权网关]:捕获越权攻击 = {}", path);
// 响应授权失败,不放行
return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0301);
}
// 认证服务下的请求放行,除刷新token接口
if (path.startsWith("auth") && !path.contains("refresh")) {
return chain.filter(exchange);
}
// 判断是否持有token以及token是否有效
String token = request.getHeaders().getFirst(HeaderConstant.TOKEN_HEADER);
if (StringUtils.isEmpty(token)) {
log.error("[鉴权网关]:缺少token = {}", path);
// 响应授权失败,不放行
return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0301);
}
try {
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource(FileConstant.PubKey)).getPath();
Payload<PayloadUserVO> infoFromToken = JwtUtils.getInfoFromToken(token, RSAUtil.readPublicKey(filePath), PayloadUserVO.class);
// 取出uid放入请求头中,方便后续过滤器使用
Long uid = infoFromToken.getInfo().getUid();
if (uid == null) {
throw new RuntimeException("载荷用户id获取失败");
}
request.mutate().header(HeaderConstant.UID_HEADER, uid.toString());
// 放行
return chain.filter(exchange);
} catch (Exception e) {
log.error("[鉴权网关]:验签异常 = {};path = {}", e.getMessage(), path);
// SignatureException 验签失败 | ExpiredJwtException 令牌过期
// 响应授权失败,不放行
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0311);
}
}
/**
* 错误响应
* @param response 响应对象
* @param errorCode 错误码
* @return 错误响应
*/
private static Mono<Void> errorResponse(ServerHttpResponse response, ErrorCodeEnum errorCode) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
byte[] jsonBytes = JSON.toJSONBytes(Result.error(errorCode));
return response.writeWith(Mono.just(response.bufferFactory().wrap(jsonBytes)));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# feign远程调用获取用户id解决方案
场景:网关解析token往请求头中放入用户id,在feign远程调用时丢失,因为feign本质是http请求,相当于会创建新的request对象。
思路: 在调用feign时通过拦截器将用户id加入请求头中发起远程调用
import cn.kk.constant.HeaderConstant;
import cn.kk.utils.RequestUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class FeignClientInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 从请求头中获取用户id
String uid = RequestUtil.getCurrentRequest().getHeader(HeaderConstant.UID_HEADER);
if (StringUtils.isNotEmpty(uid)) {
// 将用户ID添加到请求头中
template.header(HeaderConstant.UID_HEADER, uid);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
配置远程调用
@FeignClient(value = "user-service", path = "/user/api/info", configuration = FeignClientInterceptor.class)
public interface UserInfoClient {
}
2
3
# 相关理论用例
# 有状态认证和无状态认证
# 1)什么是有状态认证?
有状态认证,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。
例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。
缺点是什么?
- 服务端保存大量数据,增加服务端压力
- 服务端保存用户状态,无法进行水平扩展
- 客户端请求依赖服务端,多次请求必须访问同一台服务器
# 2)什么是无状态认证(token认证)
微服务集群中的每个服务,对外提供的都是Rest风格的接口。而Rest风格的一个最重要的规范就是:服务的无状态性,即:
- 服务端不保存任何客户端请求者信息
- 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份
带来的好处是什么呢?
- 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
- 服务端的集群和状态对客户端透明
- 服务端可以任意的迁移和伸缩
- 减小服务端存储压力
# 3)无状态分布式认证解决方案
无状态认证的流程:
- 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
- 认证通过,将用户信息进行加密形成token,返回给客户端(保存到前端 Cookie或LocalStroage中),作为登录凭证
- 客户端以后每次请求,客户端都携带Cookie中的认证信息的token
- 服务端对token进行解密,判断是否有效(身份合法性校验)。
- 服务端从token中解析出登录用户信息(用户ID,用户角色等)
流程图:

整个无状态认证过程中,最关键的点是什么?
token的安全性!!!
因为token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。所以token应该是加密的!
# JWT数据格式和工具类
# 1)JWT简介
JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io (opens new window) (JWT,生成Token加密字符串的一个标准或格式!)
GitHub上jwt的java客户端:https://github.com/jwtk/jjwt (opens new window)
# 2)JWT数据格式
JWT包含三部分数据:
- Header:头部,通常头部有两部分信息:
- 声明类型,这里是JWT
- 签名算法,自定义
我们会对头部进行base64编码,得到第一部分数据
如图所示:头部是不具备安全性的。
- Payload:载荷,就是有效数据,一般包含下面信息:
- 用户身份信息(注意,这里因为采用base64加密,可解密,因此不要存放敏感信息)
- tokenID:当前这个JWT的唯一标示
- 注册声明:如token的签发时间,过期时间,签发人等
这部分也会采用base64加密,得到第二部分数据
如图所示:载荷也不具备安全性。
- Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性
如果所示:签名中决定整个token是否安全的关键在盐。
生成的数据格式:
可以看到分为3段,每段就是上面的一部分数据
# 3)RSA非对称加密
加密技术是对信息进行编码和解码的技术,编码是把原来可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码(解密),加密技术的要点是加密算法,加密算法可以分为两大类,三小类:
- 可逆加密
- 对称加密,如AES
- 基本原理:将明文分成N个组,然后使用密钥对各个组进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密文。
- 优势:算法公开、计算量小、加密速度快、加密效率高
- 缺陷:双方都使用同样密钥,安全性得不到保证
- 非对称加密,如RSA
- 基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
- 私钥加密,持有私钥或公钥才可以解密
- 公钥加密,持有私钥才可解密
- 优点:安全,难以破解
- 缺点:算法比较耗时
- 对称加密,如AES
- 不可逆加密,如MD5,SHA,HS加密
- 基本原理:加密过程中不需要使用密钥 (opens new window),输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文。
RSA算法历史:
1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字缩写:RSA
JWT+RSA的认证流程图:
注意:
- 令牌签发:务必要使用私钥签名,公钥验证,不然会存在私钥泄露问题。
- 数据加密:使用公钥加密,私钥解密。
既然是加密,那肯定是不希望别人知道我的消息,所以只有我才能解密,所以可得出“公钥负责加密,私钥负责解密”; 既然是签名,那肯定是不希望有人冒充我发消息,只有我才能发布这个签名,所以可得出“私钥负责签名,公钥负责验证” 两者的地位相同,反过来也可以,主要是需要做到一个可暴露一个绝对不暴露
用例:
认证服务以文件形式存储私钥用于签发token令牌,网关服务用明文配置/文件的方式存储公钥用于验证令牌。
前端向服务器发送加密信息时使用公钥加密(如登录信息等),服务器使用私钥进行解密,相反,服务器向前端发送加密信息时使用私钥加密,前端使用公钥解密。
这样做即便有人截取前端信息,获得公钥,那么也只能伪造数据而不能伪造身份,因为我们是用公钥验证令牌,公钥来签名,无法用公钥来解密,所以验证失败。
# 4)RSA工具类
RsaUtils
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class RSAUtil {
/**
* 密钥类实例化入参
*/
private static final String KEY_ALGORITHM = "RSA";
private static final String PUBLIC_KEY = "RSAPublicKey";
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 245;
/**
* RSA最大解密密文大小,
* RSA 位数 如果采用1024 上面最大加密和最大解密则须填写: 117 128
* RSA 位数 如果采用2048 上面最大加密和最大解密则须填写: 245 256
*/
private static final int MAX_DECRYPT_BLOCK = 256;
/**
* 初始化密钥对生成器时,指定密钥大小的整数值(安全漏洞,长度至少为2048)
*/
private static final int KEY_PAIR_INIT_SIZE = 2048;
/**
* 从文件中读取公钥
* @param path 公钥保存路径,相对于classpath
* @return 公钥对象
*/
public static PublicKey readPublicKey(String path) throws Exception {
byte[] bytes = readFile(path);
return getPublicKey(bytes);
}
/**
* 从文件中读取密钥
* @param path 私钥保存路径,相对于classpath
* @return 私钥对象
*/
public static PrivateKey readPrivateKey(String path) throws Exception {
byte[] bytes = readFile(path);
return getPrivateKey(bytes);
}
/**
* 生成密钥对
* @return 密钥对
* @throws Exception e
*/
private static Map<String, Object> initKey() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(KEY_PAIR_INIT_SIZE);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Object> keyMap = new HashMap<>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* 获取公钥字符串
* @param keyMap 密钥对
* @return 公钥字符串
*/
private static String getPublicKeyStr(Map<String, Object> keyMap) {
// 获得 map 中的公钥对象,转为 key 对象
Key key = (Key) keyMap.get(PUBLIC_KEY);
// 编码返回字符串
return encryptBASE64(key.getEncoded());
}
/**
* 获取私钥字符串
* @param keyMap 密钥对
* @return 获取私钥字符串
*/
private static String getPrivateKeyStr(Map<String, Object> keyMap) {
// 获得 map 中的私钥对象,转为 key 对象
Key key = (Key) keyMap.get(PRIVATE_KEY);
// 编码返回字符串
return encryptBASE64(key.getEncoded());
}
/**
* 获取私钥字符串
* @param key 密钥
* @return 获取密钥字符串
*/
private static String getKeyStr(Key key) {
// 编码返回字符串
return encryptBASE64(key.getEncoded());
}
/**
* 获取公钥
* @param publicKeyString 公钥编码后的字符串
* @return 公钥
*/
private static PublicKey getPublicKey(String publicKeyString) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] publicKeyByte = Base64.getMimeDecoder().decode(publicKeyString);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyByte);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePublic(keySpec);
}
/**
* 获取私钥
* @param privateKeyString 私钥编码后的字符串
* @return 私钥
* @throws Exception e
*/
private static PrivateKey getPrivateKey(String privateKeyString) throws Exception {
byte[] privateKeyByte = Base64.getMimeDecoder().decode(privateKeyString);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyByte);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
/**
* 从文件读取
* @param filePath 文件路径
* @return 文件字节
*/
private static byte[] readFile(String filePath) throws Exception {
return Files.readAllBytes(new File(filePath).toPath());
}
/**
* 获取公钥
* @param bytes 公钥的字节形式
* @return 公钥
*/
private static PublicKey getPublicKey(byte[] bytes) throws Exception {
bytes = Base64.getMimeDecoder().decode(bytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
return factory.generatePublic(spec);
}
/**
* 获取私钥
* @param bytes 私钥的字节形式
* @return 私钥
*/
private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
bytes = Base64.getMimeDecoder().decode(bytes);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
return factory.generatePrivate(spec);
}
/**
* BASE64 编码返回加密字符串
* @param key 需要编码的字节数组
* @return 编码后的字符串
*/
private static String encryptBASE64(byte[] key) {
return Base64.getMimeEncoder().encodeToString(key);
}
/**
* 公钥加密
* @param text 待加密的明文字符串
* @param publicKeyStr 公钥
* @return 加密后的密文
*/
private static String encrypt(String text, String publicKeyStr) {
try {
log.info("明文字符串为:[{}]", text);
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKeyStr));
byte[] tempBytes = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(tempBytes);
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + text + "]时遇到异常", e);
}
}
/**
* 私钥解密
* @param secretText 待解密的密文字符串
* @param privateKeyStr 私钥
* @return 解密后的明文
*/
private static String decrypt(String secretText, String privateKeyStr) {
try {
// 生成私钥
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(privateKeyStr));
// 密文解码
byte[] secretTextDecoded = Base64.getDecoder().decode(secretText.getBytes(StandardCharsets.UTF_8));
byte[] tempBytes = cipher.doFinal(secretTextDecoded);
return new String(tempBytes);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + secretText + "]时遇到异常", e);
}
}
/**
* 公钥分段加密
* @param plainText 待加密的明文字符串
* @param publicKeyStr 公钥
* @return 加密后的密文
*/
private static String sectionEncrypt(String plainText, String publicKeyStr) throws Exception {
byte[] plainTextArray = plainText.getBytes(StandardCharsets.UTF_8);
PublicKey publicKey = getPublicKey(publicKeyStr);
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = plainTextArray.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
int i = 0;
byte[] cache;
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(plainTextArray, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(plainTextArray, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptText = out.toByteArray();
out.close();
return Base64.getEncoder().encodeToString(encryptText);
}
/**
* 私钥分段解密
* @param encryptTextHex 待解密的密文字符串
* @param privateKeyStr 私钥
* @return 解密后的明文
*/
private static String sectionDecrypt(String encryptTextHex, String privateKeyStr) throws Exception {
byte[] encryptText = Base64.getDecoder().decode(encryptTextHex);
PrivateKey privateKey = getPrivateKey(privateKeyStr);
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int inputLen = encryptText.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptText, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptText, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
out.close();
return out.toString();
}
public static void main(String[] args) throws Exception {
String cipherText;
// 原始明文
String content = "春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。";
// 生成密钥对
Map<String, Object> keyMap = initKey();
String publicKey = getKeyStr((Key) keyMap.get(PUBLIC_KEY));
// 打印公钥、私钥
System.out.println("公钥:(填充方式:PKCS1_PADDING,输出类型:base64,字符集:utf8编码)");
System.out.println("-----BEGIN PUBLIC KEY-----");
System.out.println(publicKey);
System.out.println("-----END PUBLIC KEY-----");
System.out.println("\n");
String privateKey = getKeyStr((Key) keyMap.get(PRIVATE_KEY));
System.out.println("私钥:(填充方式:PKCS1_PADDING,输出类型:base64,字符集:utf8编码)");
System.out.println("-----BEGIN RSA PRIVATE KEY-----");
System.out.println(privateKey);
System.out.println("-----END RSA PRIVATE KEY-----");
System.out.println("\n");
// 加密
cipherText = sectionEncrypt(content, publicKey);
log.info("加密后的密文:[{}],长度:[{}]", cipherText, cipherText.length());
// 解密
String plainText = sectionDecrypt(cipherText, privateKey);
log.info("解密后明文:[{}]", plainText);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
RSA工具类负责对RSA密钥的创建、读取功能:
提示
调用工具类main方法生成公私钥,将控制台打印的公私钥复制到文件中
公钥:(填充方式:PKCS1_PADDING,输出类型:base64,字符集:utf8编码)
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4fBx21LQ+qW2p58qn60CEV6t/3ohQTAM
gkR+1FJlOZl5Rk9G9CvNDE9jLW8Ac5/kRQOKy6CwmsqiemlbHLP80DAgOV4Gpf6qrihux01M5mSm
ss4DxVKEJoN5b86GplVkyz7nuIkb68eKfxQ1FEiUWMx4b3WCXxv4EUFruoj05CzzHzGquhx8q9bC
iEWL2d6VprVCzNGszPB5y29lsw48KSbyPNhza01lT7WOi/RJmXwnH6/Ur6Alu2w2beEbCmGVLXKQ
542lN4rBT7BFRhp12CsgWqUwiKg+y6uXqaXa186St+/9hyf5f+NGd3mgefFMkZ1w+Cb7G2wgrVw5
xiRscwIDAQAB
-----END PUBLIC KEY-----
私钥:(填充方式:PKCS1_PADDING,输出类型:base64,字符集:utf8编码)
-----BEGIN RSA PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDh8HHbUtD6pbannyqfrQIRXq3/
eiFBMAyCRH7UUmU5mXlGT0b0K80MT2MtbwBzn+RFA4rLoLCayqJ6aVscs/zQMCA5Xgal/qquKG7H
TUzmZKayzgPFUoQmg3lvzoamVWTLPue4iRvrx4p/FDUUSJRYzHhvdYJfG/gRQWu6iPTkLPMfMaq6
HHyr1sKIRYvZ3pWmtULM0azM8HnLb2WzDjwpJvI82HNrTWVPtY6L9EmZfCcfr9SvoCW7bDZt4RsK
YZUtcpDnjaU3isFPsEVGGnXYKyBapTCIqD7Lq5eppdrXzpK37/2HJ/l/40Z3eaB58UyRnXD4Jvsb
bCCtXDnGJGxzAgMBAAECgf9HC9TBoskUCAfnraU5kK2VyK/uJflPBkzoqaE4IrCx7BC2izdEkIFp
3YzSPJVnxl+BVBvQ7DacdhuhYn/EBtT4oil/4JE2oV4KZaxv/16vE/dXQnDtG4VyluUYw1iT9UY8
ows6e+LLwXNLgo8uUaRUR/eXFNxqC+F1i5zUizx2gxR9kC+GeAeV66dnN8oD8kG/qA+kL4leEbwv
QSjl911s+G3zQPqYsZcTa3LlgclXOpb9k7ny0Jt5Gf7Vk49L+xM89Q9UXifw8KtfH+s1qAMO5O9x
PGISW8jIc+vu6n+8vkrJzYQ9fwFfCt2u7By6jG1OFmHewuLV4tBKU28g2nUCgYEA5JLBnnB55EpD
44l3vyEv5Cgw49sv3sdGECvWE66TpHmxFha2PGsf5q7fr+d3J5WVvijA6Lstocjf6p4oFrLp5quO
lGkH43NU5QJ8R9mjeQKhlBSUcOO7mcx4NX/f7WhRnGYmfcD2+/jjKdWsOB1YQJWgIzQfIx85Rif5
i6uNpw0CgYEA/QzG8PmkAr30sXGyKDP1YV790hI9EKK+wdwN49EyM9rqZSWM+XbiKxZ3+kILqBCS
W4veVNFVt4D9SmzpDkkHMclXHPkv8HxaFI112WEYeC1J91PU4aW+/ZcXs55dMuknAVlvfHUO0W1o
TTTC7XJY8wJNzRsHB2EY8DieY4Q1gX8CgYEAqIIU4bRz+DxT3lxK/on934tNt72wheGNIoDeuvXZ
/MiGKA2oXdRv2UYUvH69Fl6t3Vn4FdtgYcRQZ4wfa9SMobTaUUk5OpKdBJQXgXTOknjBHAUsBimt
9U2LVY2nTUO53r5kJBNtQgeTmRTgeSIRHNPyEeHsDp2HQNRZoYFg1cUCgYEAkxpkZB2bfISUxLqF
iN17nhkskHxNx2f0YJnd8TvhZUezzwzfQP4dSFMNeAwA3riTfGh43PG8VKrFlAhwCv/lEMosvAkY
F0MS0rPReIb7RJF/WCoBrRM/TggRnTdIkpn7NMubBcy+ynxBj/BGCObaUppaRAucMGv/pVYkrVFk
EV0CgYEAr2kodVJ5DqK2RqR/vEbHjufi9K6dgh+d+RGMqgNAx178LzH+/bGceLzns81YyKUTGaJm
vchR/ojDA4kcTlcyWWDF5rOOTRaXqselDd61KCZV0w4IqevP6aiSgQ8UjEQvIWcRhPgwsLwQT8PJ
AKE0g5A/i0o08n3nrWrCn7gw0AI=
-----END RSA PRIVATE KEY-----
20:12:59.005 [main] INFO cn.kk.utils.RSAUtil -- 加密后的密文:[jrlhPoE0ynvwX5OBEzWbyhAlMr/W5zYxDB51Gtl33bxV21k0d5rRoxCLCNBRK4sW5R/sqqAwl3l1iW+HmbVE2jC6ozfCKabh/ylUVvQSinKM5m5niySXiEi6YK2DhoGwSzK6uRY9zA7UkUZMpAM1Nph84vQjUIk5zyfGmXTxX/fxAwKg2MQGffkWnghOKfC7qYR+U1NDkS8EtH/kqL5IgZ5uEFd/ZOtnt1nVXbyg5sQhZGeuhz11AAH31SrTnDItnhuFIGyS3MO4FtuHf0ZgGdWJPoewiMIIokqaiYZwD1eF0jW6LR3D0EVonhjbkc6oX/MW9Q4gzTbSY8F9XRrfqQ==],长度:[344]
20:12:59.012 [main] INFO cn.kk.utils.RSAUtil -- 解密后明文:[春江潮水连海平,海上明月共潮生。滟滟随波千万里,何处春江无月明。]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
生成公私钥如下:
提示
使用工具类读取
import com.heima.utils.common.RsaUtils;
import org.junit.Test;
public class RsaTest {
public String pubKeyPath = "D:\\idea_codes\\test\\rsa\\rsa-key.pub";
public String priKeyPath = "D:\\idea_codes\\test\\rsa\\rsa-key";
/**
* 获取公私钥匙
*/
@Test
public void testGen() throws Exception {
String preKey = RSAUtil.readPrivateKey(preKeyPath);
String publicKey = RSAUtil.readPublicKey(pubKeyPath);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 5)JWT工具类
import cn.kk.base.model.Payload;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.UUID;
public class JwtUtils {
private static final String JWT_PAYLOAD_USER_KEY = "user";
/**
* 私钥加密token
* @param info 载荷中的数据
* @param privateKey 私钥
* @param expire 过期时间,单位分钟
* @return JWT
*/
public static String generateTokenExpireInMinutes(Object info, PrivateKey privateKey, int expire) {
return Jwts.builder()
//claim: 往Jwt的载荷存入数据
.claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(info))
//往Jwt的载荷存入数据,设置固定id的key
.setId(createJTI())
//往Jwt的载荷存入数据,设置固定exp的key
.setExpiration(DateUtils.toDate(LocalDateTime.now().plusMinutes(expire)))
//设置token的签名
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
/**
* 私钥加密token
* @param info 载荷中的数据
* @param privateKey 私钥
* @param expire 过期时间,单位秒
* @return JWT
*/
public static String generateTokenExpireInSeconds(Object info, PrivateKey privateKey, int expire) {
return Jwts.builder()
.claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(info))
.setId(createJTI())
.setExpiration(DateUtils.toDate(LocalDateTime.now().plusSeconds(expire)))
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
/**
* 公钥解析token
* @param token 用户请求中的token
* @param publicKey 公钥
* @return Jws<Claims>
*/
private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
}
/**
* 生成JWT ID
* @return ID
*/
private static String createJTI() {
return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
}
/**
* 获取token中的用户信息
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
Payload<T> claims = new Payload<>();
claims.setId(body.getId());
claims.setInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
claims.setExpiration(body.getExpiration());
return claims;
}
/**
* 获取token中的载荷信息
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
Payload<T> claims = new Payload<>();
claims.setId(body.getId());
claims.setExpiration(body.getExpiration());
return claims;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
Payload载荷:
import lombok.Data;
import java.util.Date;
@Data
public class Payload<T> {
private String id;
private T info;
private Date expiration;
}
2
3
4
5
6
7
8
9
10
测试
package com.heima;
import com.heima.utils.common.JwtUtils;
import com.heima.utils.common.Payload;
import com.heima.utils.common.RsaUtils;
import org.junit.Test;
import java.security.PrivateKey;
import java.security.PublicKey;
public class JwtTest {
public static final String publicKeyPath = "D:\\idea_codes\\javaee164\\rsa\\rsa-key.pub";
public static final String privateKeyPath = "D:\\idea_codes\\javaee164\\rsa\\rsa-key";
/**
* 生成Token
*/
@Test
public void testGenerateToken() throws Exception {
/**
* 参数一:载荷中用户信息
* 参数二:私钥信息
* 参数三:过期时间(分钟)
*/
Integer userId = 1001;
PrivateKey privateKey = RsaUtils.getPrivateKey(privateKeyPath);
String token = JwtUtils.generateTokenExpireInMinutes(userId, privateKey, 1);
System.out.println(token);
}
/**
* 校验token合法
*/
@Test
public void testVerifyToken() throws Exception {
/**
* 参数一:需要验证的token字符串
* 参数二:公钥信息
* 参数三:载荷用户信息类型
*/
String token = "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyIjoiMTAwMSIsImp0aSI6Ik1UYzJZemMxT0RVdE5qZzNOeTAwTnpSbExUaG1Zamd0TTJWbU5HWTRZemswWkdWaiIsImV4cCI6MTY0OTA1NDk3M30.SpfshCC7rGfbToAmJKRIO7SB2QJSCgnu2MxZ54Z6y-l3tjPpI559o0V4e8WaN918TiZlXTFGxA33f0e6Ezo1uZbnXNOYclTcukUMUrmyhHyCDl-oYNLXNekLmgzV_RDzP3QZzdZlpuEoa7yrUCc5T6mMvl7ogJ8vbKGjBduufDtOH-qW8JYAGkiEfT6-g4-mfRkAaLFGXul6S2y17yFR1m3uznwu5WVZi7mqMcxpV4r11FjDvgpMl-6oSznZVAT0VYnQvtQukal6M5ilMndZ1rS0AaVykjOesdNkkXQg06lvvt4C50KsTUrLfl_vAtsbvJLijeptL9ZWoFcmRmjcuw";
PublicKey publicKey = RsaUtils.getPublicKey(publicKeyPath);
//getInfoFromToken: 如果报错,代表验证失败了
Payload<Integer> payload = JwtUtils.getInfoFromToken(token, publicKey, Integer.class);
//取出用户信息
Integer userId = payload.getInfo();
System.out.println("登录用户信息:"+userId);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 密码加密方式
# 1)MD5密码加密
//md5加密 DegestUtils:spring框架提供的工具类
String md5Str = DigestUtils.md5DigestAsHex("abc".getBytes());
System.out.println(md5Str);//900150983cd24fb0d6963f7d28e17f72
2
3
md5相同的密码每次加密都一样,不太安全
# 2)BCrypt密码加密(随机盐)
在用户模块,对于用户密码的保护,通常都会进行加密。我们通常对密码进行加密,然后存放在数据库中,在用户进行登录的时候,将其输入的密码进行加密然后与数据库中存放的密文进行比较,以验证用户密码是否正确。 目前,MD5和BCrypt比较流行。
BCrypt 官网http://www.mindrot.org/projects/jBCrypt/ (opens new window)
BCrypt
BCrypt工具
// Copyright (c) 2006 Damien Miller <djm@mindrot.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package com.heima.utils.common;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
/**
* BCrypt implements OpenBSD-style Blowfish password hashing using
* the scheme described in "A Future-Adaptable Password Scheme" by
* Niels Provos and David Mazieres.
* <p>
* This password hashing system tries to thwart off-line password
* cracking using a computationally-intensive hashing algorithm,
* based on Bruce Schneier's Blowfish cipher. The work factor of
* the algorithm is parameterised, so it can be increased as
* computers get faster.
* <p>
* Usage is really simple. To hash a password for the first time,
* call the hashpw method with a random salt, like this:
* <p>
* <code>
* String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt()); <br />
* </code>
* <p>
* To check whether a plaintext password matches one that has been
* hashed previously, use the checkpw method:
* <p>
* <code>
* if (BCrypt.checkpw(candidate_password, stored_hash))<br />
* System.out.println("It matches");<br />
* else<br />
* System.out.println("It does not match");<br />
* </code>
* <p>
* The gensalt() method takes an optional parameter (log_rounds)
* that determines the computational complexity of the hashing:
* <p>
* <code>
* String strong_salt = BCrypt.gensalt(10)<br />
* String stronger_salt = BCrypt.gensalt(12)<br />
* </code>
* <p>
* The amount of work increases exponentially (2**log_rounds), so
* each increment is twice as much work. The default log_rounds is
* 10, and the valid range is 4 to 30.
*
* @author Damien Miller
* @version 0.2
*/
public class BCrypt {
// BCrypt parameters
private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10;
private static final int BCRYPT_SALT_LEN = 16;
// Blowfish parameters
private static final int BLOWFISH_NUM_ROUNDS = 16;
// Initial contents of key schedule
private static final int P_orig[] = {
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
0x9216d5d9, 0x8979fb1b
};
private static final int S_orig[] = {
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
};
// bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls
// this "ciphertext", but it is really plaintext or an IV. We keep
// the name to make code comparison easier.
static private final int bf_crypt_ciphertext[] = {
0x4f727068, 0x65616e42, 0x65686f6c,
0x64657253, 0x63727944, 0x6f756274
};
// Table for Base64 encoding
static private final char base64_code[] = {
'.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9'
};
// Table for Base64 decoding
static private final byte index_64[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 0, 1, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63, -1, -1,
-1, -1, -1, -1, -1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
-1, -1, -1, -1, -1, -1, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, -1, -1, -1, -1, -1
};
// Expanded Blowfish key
private int P[];
private int S[];
/**
* Encode a byte array using bcrypt's slightly-modified base64
* encoding scheme. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
*
* @param d the byte array to encode
* @param len the number of bytes to encode
* @return base64-encoded string
* @exception IllegalArgumentException if the length is invalid
*/
private static String encode_base64(byte d[], int len)
throws IllegalArgumentException {
int off = 0;
StringBuffer rs = new StringBuffer();
int c1, c2;
if (len <= 0 || len > d.length)
throw new IllegalArgumentException ("Invalid len");
while (off < len) {
c1 = d[off++] & 0xff;
rs.append(base64_code[(c1 >> 2) & 0x3f]);
c1 = (c1 & 0x03) << 4;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 4) & 0x0f;
rs.append(base64_code[c1 & 0x3f]);
c1 = (c2 & 0x0f) << 2;
if (off >= len) {
rs.append(base64_code[c1 & 0x3f]);
break;
}
c2 = d[off++] & 0xff;
c1 |= (c2 >> 6) & 0x03;
rs.append(base64_code[c1 & 0x3f]);
rs.append(base64_code[c2 & 0x3f]);
}
return rs.toString();
}
/**
* Look up the 3 bits base64-encoded by the specified character,
* range-checking againt conversion table
* @param x the base64-encoded value
* @return the decoded value of x
*/
private static byte char64(char x) {
if ((int)x < 0 || (int)x > index_64.length)
return -1;
return index_64[(int)x];
}
/**
* Decode a string encoded using bcrypt's base64 scheme to a
* byte array. Note that this is *not* compatible with
* the standard MIME-base64 encoding.
* @param s the string to decode
* @param maxolen the maximum number of bytes to decode
* @return an array containing the decoded bytes
* @throws IllegalArgumentException if maxolen is invalid
*/
private static byte[] decode_base64(String s, int maxolen)
throws IllegalArgumentException {
StringBuffer rs = new StringBuffer();
int off = 0, slen = s.length(), olen = 0;
byte ret[];
byte c1, c2, c3, c4, o;
if (maxolen <= 0)
throw new IllegalArgumentException ("Invalid maxolen");
while (off < slen - 1 && olen < maxolen) {
c1 = char64(s.charAt(off++));
c2 = char64(s.charAt(off++));
if (c1 == -1 || c2 == -1)
break;
o = (byte)(c1 << 2);
o |= (c2 & 0x30) >> 4;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c3 = char64(s.charAt(off++));
if (c3 == -1)
break;
o = (byte)((c2 & 0x0f) << 4);
o |= (c3 & 0x3c) >> 2;
rs.append((char)o);
if (++olen >= maxolen || off >= slen)
break;
c4 = char64(s.charAt(off++));
o = (byte)((c3 & 0x03) << 6);
o |= c4;
rs.append((char)o);
++olen;
}
ret = new byte[olen];
for (off = 0; off < olen; off++)
ret[off] = (byte)rs.charAt(off);
return ret;
}
/**
* Blowfish encipher a single 64-bit block encoded as
* two 32-bit halves
* @param lr an array containing the two 32-bit half blocks
* @param off the position in the array of the blocks
*/
private final void encipher(int lr[], int off) {
int i, n, l = lr[off], r = lr[off + 1];
l ^= P[0];
for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) {
// Feistel substitution on left word
n = S[(l >> 24) & 0xff];
n += S[0x100 | ((l >> 16) & 0xff)];
n ^= S[0x200 | ((l >> 8) & 0xff)];
n += S[0x300 | (l & 0xff)];
r ^= n ^ P[++i];
// Feistel substitution on right word
n = S[(r >> 24) & 0xff];
n += S[0x100 | ((r >> 16) & 0xff)];
n ^= S[0x200 | ((r >> 8) & 0xff)];
n += S[0x300 | (r & 0xff)];
l ^= n ^ P[++i];
}
lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1];
lr[off + 1] = l;
}
/**
* Cycically extract a word of key material
* @param data the string to extract the data from
* @param offp a "pointer" (as a one-entry array) to the
* current offset into data
* @return the next word of material from data
*/
private static int streamtoword(byte data[], int offp[]) {
int i;
int word = 0;
int off = offp[0];
for (i = 0; i < 4; i++) {
word = (word << 8) | (data[off] & 0xff);
off = (off + 1) % data.length;
}
offp[0] = off;
return word;
}
/**
* Initialise the Blowfish key schedule
*/
private void init_key() {
P = (int[])P_orig.clone();
S = (int[])S_orig.clone();
}
/**
* Key the Blowfish cipher
* @param key an array containing the key
*/
private void key(byte key[]) {
int i;
int koffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamtoword(key, koffp);
for (i = 0; i < plen; i += 2) {
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* Perform the "enhanced key schedule" step described by
* Provos and Mazieres in "A Future-Adaptable Password Scheme"
* http://www.openbsd.org/papers/bcrypt-paper.ps
* @param data salt information
* @param key password information
*/
private void ekskey(byte data[], byte key[]) {
int i;
int koffp[] = { 0 }, doffp[] = { 0 };
int lr[] = { 0, 0 };
int plen = P.length, slen = S.length;
for (i = 0; i < plen; i++)
P[i] = P[i] ^ streamtoword(key, koffp);
for (i = 0; i < plen; i += 2) {
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
P[i] = lr[0];
P[i + 1] = lr[1];
}
for (i = 0; i < slen; i += 2) {
lr[0] ^= streamtoword(data, doffp);
lr[1] ^= streamtoword(data, doffp);
encipher(lr, 0);
S[i] = lr[0];
S[i + 1] = lr[1];
}
}
/**
* Perform the central password hashing step in the
* bcrypt scheme
* @param password the password to hash
* @param salt the binary salt to hash with the password
* @param log_rounds the binary logarithm of the number
* of rounds of hashing to apply
* @param cdata the plaintext to encrypt
* @return an array containing the binary hashed password
*/
public byte[] crypt_raw(byte password[], byte salt[], int log_rounds,
int cdata[]) {
int rounds, i, j;
int clen = cdata.length;
byte ret[];
if (log_rounds < 4 || log_rounds > 30)
throw new IllegalArgumentException ("Bad number of rounds");
rounds = 1 << log_rounds;
if (salt.length != BCRYPT_SALT_LEN)
throw new IllegalArgumentException ("Bad salt length");
init_key();
ekskey(salt, password);
for (i = 0; i != rounds; i++) {
key(password);
key(salt);
}
for (i = 0; i < 64; i++) {
for (j = 0; j < (clen >> 1); j++)
encipher(cdata, j << 1);
}
ret = new byte[clen * 4];
for (i = 0, j = 0; i < clen; i++) {
ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
ret[j++] = (byte)(cdata[i] & 0xff);
}
return ret;
}
/**
* Hash a password using the OpenBSD bcrypt scheme
* @param password the password to hash
* @param salt the salt to hash with (perhaps generated
* using BCrypt.gensalt)
* @return the hashed password
*/
public static String hashpw(String password, String salt) {
BCrypt B;
String real_salt;
byte passwordb[], saltb[], hashed[];
char minor = (char)0;
int rounds, off = 0;
StringBuffer rs = new StringBuffer();
if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
throw new IllegalArgumentException ("Invalid salt version");
if (salt.charAt(2) == '$')
off = 3;
else {
minor = salt.charAt(2);
if (minor != 'a' || salt.charAt(3) != '$')
throw new IllegalArgumentException ("Invalid salt revision");
off = 4;
}
// Extract number of rounds
if (salt.charAt(off + 2) > '$')
throw new IllegalArgumentException ("Missing salt rounds");
rounds = Integer.parseInt(salt.substring(off, off + 2));
real_salt = salt.substring(off + 3, off + 25);
try {
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
throw new AssertionError("UTF-8 is not supported");
}
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
B = new BCrypt();
hashed = B.crypt_raw(passwordb, saltb, rounds,
(int[])bf_crypt_ciphertext.clone());
rs.append("$2");
if (minor >= 'a')
rs.append(minor);
rs.append("$");
if (rounds < 10)
rs.append("0");
if (rounds > 30) {
throw new IllegalArgumentException(
"rounds exceeds maximum (30)");
}
rs.append(Integer.toString(rounds));
rs.append("$");
rs.append(encode_base64(saltb, saltb.length));
rs.append(encode_base64(hashed,
bf_crypt_ciphertext.length * 4 - 1));
return rs.toString();
}
/**
* Generate a salt for use with the BCrypt.hashpw() method
* @param log_rounds the log2 of the number of rounds of
* hashing to apply - the work factor therefore increases as
* 2**log_rounds.
* @param random an instance of SecureRandom to use
* @return an encoded salt value
*/
public static String gensalt(int log_rounds, SecureRandom random) {
StringBuffer rs = new StringBuffer();
byte rnd[] = new byte[BCRYPT_SALT_LEN];
random.nextBytes(rnd);
rs.append("$2a$");
if (log_rounds < 10)
rs.append("0");
if (log_rounds > 30) {
throw new IllegalArgumentException(
"log_rounds exceeds maximum (30)");
}
rs.append(Integer.toString(log_rounds));
rs.append("$");
rs.append(encode_base64(rnd, rnd.length));
return rs.toString();
}
/**
* Generate a salt for use with the BCrypt.hashpw() method
* @param log_rounds the log2 of the number of rounds of
* hashing to apply - the work factor therefore increases as
* 2**log_rounds.
* @return an encoded salt value
*/
public static String gensalt(int log_rounds) {
return gensalt(log_rounds, new SecureRandom());
}
/**
* Generate a salt for use with the BCrypt.hashpw() method,
* selecting a reasonable default for the number of hashing
* rounds to apply
* @return an encoded salt value
*/
public static String gensalt() {
return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS);
}
/**
* Check that a plaintext password matches a previously hashed
* one
* @param plaintext the plaintext password to verify
* @param hashed the previously-hashed password
* @return true if the passwords match, false otherwise
*/
public static boolean checkpw(String plaintext, String hashed) {
byte hashed_bytes[];
byte try_bytes[];
try {
String try_pw = hashpw(plaintext, hashed);
hashed_bytes = hashed.getBytes("UTF-8");
try_bytes = try_pw.getBytes("UTF-8");
} catch (UnsupportedEncodingException uee) {
return false;
}
if (hashed_bytes.length != try_bytes.length)
return false;
byte ret = 0;
for (int i = 0; i < try_bytes.length; i++)
ret |= hashed_bytes[i] ^ try_bytes[i];
return ret == 0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
String gensalt = BCrypt.gensalt();//这个是盐 29个字符,随机生成
System.out.println(gensalt);
String password = BCrypt.hashpw("123456", gensalt); //根据盐对密码进行加密
System.out.println(password);//加密后的字符串前29位就是盐
2
3
4
新建测试类,main方法中编写代码,实现对密码的校验。BCrypt不支持反运算,只支持密码校验。
boolean checkpw = BCrypt.checkpw("123456", "$2a$10$61ogZY7EXsMDWeVGQpDq3OBF1.phaUu7.xrwLyWFTOu8woE08zMIW");
System.out.println(checkpw);
2
测试类:
import com.heima.utils.common.BCrypt;
import org.junit.Test;
public class BCryptTest {
/**
* 生成加密密码
*/
@Test
public void testEncode(){
String pwd = "admin";
//生成随机盐
String salt = BCrypt.gensalt();
String encodePwd = BCrypt.hashpw(pwd, salt);
System.out.println(salt);
System.out.println(encodePwd);
}
/**
* 校验密码是否正确
*/
@Test
public void testVerifyPwd(){
String pwd = "admin";
String encodePwd = "$2a$10$RPCGJfDaQu0u4RWfHxECVuUZ5EXa67IQ4/718L2MiEPv3159kiTXG";
boolean result = BCrypt.checkpw(pwd, encodePwd);
System.out.println(result);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29