使用rsa进行http传输加密
© 版权声明:本文为博主原创文章,转载请注明出处
1. RSA算法
RSA是目前最有影响力和最常用的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。
今天只有短的RSA钥匙才可能被强力方式破解。但在分布式计算和量子计算机理论日趋成熟的今天,RSA加密安全性收到了挑战和质疑。
RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解缺及其困难,因此可以将乘积公开作为加密密钥。
2. HTTPS
2.1 HTTPS优点
1. 使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器。
2. HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
3. HTTPS是现行框架下最安全的解决方案,虽然不是觉得安全,但它增加了中间人攻击的成本。
2.2 HTTPS缺点
1. SSL的专业证书需要购买,功能越强大的证书费用越高
2. 相同的网络环境下,HTTPS协议会使页面的加载时间延长50%,增加10%-20%的耗电。此外,HTTPS协议还会影响缓存,增加数据开销和功耗。
3. HTTPS协议的安全性是有范围的,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。
4. 最关键的是,SSL证书的信用链体系并不安全。特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。
3. RSA传输加密实现
综上所述(其实主要是因为HTTPS购买SSL证书需要花钱),可在某些关键数据传输过程中进行RSA加密。比如:登录时对登录密码进行加密。
3.1 所需插件
3.1.1 JS插件
BigInt.js  -   用于生成一个大整数(这是RSA算法的需要)
Barrett.js -   RSA算法所需要用到的一个支持文件
RSA_Stripped.js    -   RSA的主要算法
下载密码:bhiq
3.1.2 所需JAR
bcprov-jdk15on
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.58</version>
</dependency>
3.1.3 代码
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  	<modelVersion>4.0.0</modelVersion>
	<groupId>com.study</groupId>
	<artifactId>webrsa</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<!-- bcprov-jdk15on -->
		<dependency>
		    <groupId>org.bouncycastle</groupId>
		    <artifactId>bcprov-jdk15on</artifactId>
		    <version>1.58</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.6.1</version>
				<configuration>
					<target>1.7</target>
					<source>1.7</source>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>
RSAUtils.java
package com.study.webrsa.utils;
import java.io.ByteArrayOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
 * RSA加解密工具类
 *
 */
public class RSAUtils {
	public static final String SECURITY = "RSA"; // 加密方式
	public static final String ALGORITHM = "MD5withRSA"; // 加密算法
	public static final String PUBLIC_KEY = "RSAPublicKey"; // 公钥
	public static final String PRIVATE_KEY = "RSAPrivateKey"; // 私钥
	/**
	 * 获取密钥
	 */
	public static Map<String, Object> getKey() {
		Map<String, Object> map = null;
		try {
			// 生成实现指定算法的KeyPairGenerator对象,用于生成密钥对
			KeyPairGenerator keyPairGenerator =
					KeyPairGenerator.getInstance(SECURITY, new BouncyCastleProvider());
			keyPairGenerator.initialize(1024); // 初始化密钥长度
			KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 生成密钥对
			RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); // 获取公钥
			RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); // 获取私钥
			// 保存到map中
			map = new HashMap<String, Object>();
			map.put(PUBLIC_KEY, rsaPublicKey);
			map.put(PRIVATE_KEY, rsaPrivateKey);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return map;
	}
	/**
	 * 利用私钥进行解密
	 *
	 * @param privateKey
	 * 						私钥
	 * @param str
	 * 						密文
	 * @return
	 */
	public static String decrypt(RSAPrivateKey privateKey, String str) {
		try {
			System.out.println("密文为:" + str);
			// 获取实现指定转换的Cipher对象
			Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding", new BouncyCastleProvider());
			cipher.init(Cipher.DECRYPT_MODE, privateKey); // 用密钥初始化此Cipher对象
			int blockSize = cipher.getBlockSize(); // 返回块的大小
			byte[] bytes = hexStringToBytes(str); // 将十六进制转换为二进制
			int j = 0;
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			while (bytes.length - j * blockSize > 0) { // 将二进制数据分块写入ByteArrayOutputStream中
				baos.write(cipher.doFinal(bytes, j * blockSize, blockSize));
				j++;
			}
			// 将二进制数据转换为字符串
			byte[] bs = baos.toByteArray();
			StringBuilder sb = new StringBuilder();
			sb.append(new String(bs));
			String pwd = sb.reverse().toString();
			System.out.println("明文为:" + pwd);
			return pwd;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 将十六进制字符串转换为二进制数组
	 *
	 * @param hexString
	 * 					十六进制字符串
	 * @return
	 */
	private static byte[] hexStringToBytes(String hexString) {
		if (hexString == null || "".equals(hexString)) {
			return null;
		}
		hexString = hexString.toUpperCase(); // 全部转换为大写字符
		int length = hexString.length() / 2; // 获取十六进制数据个数
		char[] hexChars = hexString.toCharArray(); // 将十六进制字符串转换为字符数组
		byte[] d = new byte[length];
		for (int i = 0; i < length; i++) {
			int pos = i * 2; // 开始位置
			d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
		}
		return d;
	}
	private static byte charToByte(char ch) {
		return (byte) "0123456789ABCDEF".indexOf(ch);
	}
}
login.jsp
<%@page import="java.util.Map"%>
<%@page import="java.security.interfaces.RSAPrivateKey"%>
<%@page import="java.security.interfaces.RSAPublicKey"%>
<%@page import="com.study.webrsa.utils.RSAUtils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>用户登录</title>
	<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
	<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<%
	// 获取密钥对
	Map<String, Object> map = RSAUtils.getKey();
	// 获取公钥
	RSAPublicKey rsaPublicKey = (RSAPublicKey) map.get(RSAUtils.PUBLIC_KEY);
	// 获取私钥
	RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) map.get(RSAUtils.PRIVATE_KEY);
	// 保存私钥到session中,便于后台进行解密
	session.setAttribute("rsaKey", rsaPrivateKey);
	// 保存公钥到request中,便于页面加密
	String publicExponent = rsaPublicKey.getPublicExponent().toString(16);
	String publicModulus = rsaPublicKey.getModulus().toString(16);
	request.setAttribute("publicExponent", publicExponent);
	request.setAttribute("publicModulus", publicModulus);
%>
<body>
	<div class="container-fluid">
		<form action="login" method="post" class="col-md-6 col-md-offset-3"
			onsubmit="return cmdEncrypt();">
			<div class="form-group">
				<label for="loginName">登录名</label>
				<input type="text" id="loginName" name="loginName" class="form-control"
					placeholder="请输入用户名...">
			</div>
			<div class="form-group">
				<label for="loginPwd">登录密码</label>
				<input type="password" id="loginPwd" name="loginPwd" class="form-control"
					placeholder="请输入登录密码...">
			</div>
			<button type="submit" class="btn btn-primary">登录</button>
		</form>
	</div>
</body>
	<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
	<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
	<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
	<script type="text/javascript" src="resources/rsa/BigInt.js"></script>
	<script type="text/javascript" src="resources/rsa/Barrett.js"></script>
	<script type="text/javascript" src="resources/rsa/RSA_Stripped.js"></script>
	<script type="text/javascript">
		// 提交前对密码进行加密
		function cmdEncrypt() {
			setMaxDigits(131);
			var pwd = $("#loginPwd").val(); // 获取原始密码
			var key = new RSAKeyPair("${publicExponent}", "", "${publicModulus}");
			pwd = encryptedString(key, pwd); // 对密码进行加密
			$("#loginPwd").val(pwd);
			return true;
		}
	</script>
</html>
LoginServlet.java
package com.study.webrsa.servlet;
import java.io.IOException;
import java.security.interfaces.RSAPrivateKey;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.study.webrsa.utils.RSAUtils;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doPost(req, resp);
	}
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		// 设置编码格式
		req.setCharacterEncoding("UTF-8");
		resp.setCharacterEncoding("UTF-8");
		// 获取前台参数
		String loginName = req.getParameter("loginName");
		String loginPwd = req.getParameter("loginPwd");
		// 获取私钥
		RSAPrivateKey privateKey = (RSAPrivateKey) req.getSession().getAttribute("rsaKey");
		// 对密码进行解密
		loginPwd = RSAUtils.decrypt(privateKey, loginPwd);
		// 校验
		if (true) {
			req.setAttribute("username", loginName);
			System.out.println("用户[" + loginName + "]用密码[" + loginPwd + "]登录本系统");
			req.getRequestDispatcher("/success.jsp").forward(req, resp);
		}
	}
}
success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登录成功</title>
</head>
<body>
	<center>
		<h1>欢迎您,${username }</h1>
	</center>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
	                    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	version="3.0">
	<welcome-file-list>
		<welcome-file>login.jsp</welcome-file>
	</welcome-file-list>
</web-app>
4. 注意事项
4.1 setMaxDigits()
setMaxDigits(),到底应该传值多少?
在JS文件中给出公式为:n * 2 / 16。其中n为密钥长度。
    如果n为1024,则值应为 1024 * 2 / 16 = 128。
经过测试,传128后台解密会报错;正确的值应该大于128。
个人喜好的公式是:n * 2 / 16 + 3
即  密钥长度若为1024,其值为 131
    密钥长度若为2048,其值为 259
4.2 解密方式
在网上百度的代码,解密方式一般如下所示:
// 获取实现指定转换的Cipher对象
Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding", new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, privateKey); // 用密钥初始化此Cipher对象
int blockSize = cipher.getBlockSize(); // 返回块的大小
byte[] bytes = new BigInteger(str, 16).toByteArray();
int j = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while (bytes.length - j * blockSize > 0) { // 将二进制数据分块写入ByteArrayOutputStream中
	baos.write(cipher.doFinal(bytes, j * blockSize, blockSize));
	j++;
}
用上述方式,偶尔会报错如下所示:
java.lang.IllegalArgumentException: Bad arguments
	at javax.crypto.Cipher.doFinal(Cipher.java:2185)
	at com.study.webrsa.utils.RSAUtils.decrypt(RSAUtils.java:76)
	at com.study.webrsa.servlet.LoginServlet.doPost(LoginServlet.java:43)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:962)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:445)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1087)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:745)
后发现问题就出现在toByteArray()上面,因为在用上面的三个JS进行加密时,偶尔得出的密文会比正确的密文多出一个byte,里面是o。
因此可使用如下方式:
// 获取实现指定转换的Cipher对象
Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding", new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, privateKey); // 用密钥初始化此Cipher对象
int blockSize = cipher.getBlockSize(); // 返回块的大小
byte[] bytes = hexStringToBytes(str); // 将十六进制转换为二进制
int j = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while (bytes.length - j * blockSize > 0) { // 将二进制数据分块写入ByteArrayOutputStream中
	baos.write(cipher.doFinal(bytes, j * blockSize, blockSize));
	j++;
}
/**
 * 将十六进制字符串转换为二进制数组
 *
 * @param hexString
 * 					十六进制字符串
 * @return
 */
private static byte[] hexStringToBytes(String hexString) {
	if (hexString == null || "".equals(hexString)) {
		return null;
	}
	hexString = hexString.toUpperCase(); // 全部转换为大写字符
	int length = hexString.length() / 2; // 获取十六进制数据个数
	char[] hexChars = hexString.toCharArray(); // 将十六进制字符串转换为字符数组
	byte[] d = new byte[length];
	for (int i = 0; i < length; i++) {
		int pos = i * 2; // 开始位置
		d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
	}
	return d;
}
private static byte charToByte(char ch) {
	return (byte) "0123456789ABCDEF".indexOf(ch);
}
参考:
JS加密Java解密报rsa bad argument
HTTPS优缺点、原理解析:我们的网站该不该做HTTPS?
更好的markdown体验:https://www.zybuluo.com/chy282/note/975080
使用rsa进行http传输加密的更多相关文章
- iOS中使用RSA对数据进行加密解密
		
RSA算法是一种非对称加密算法,常被用于加密数据传输.如果配合上数字摘要算法, 也可以用于文件签名. 本文将讨论如何在iOS中使用RSA传输加密数据. 本文环境 mac os openssl-1.0. ...
 - NET实现RSA AES DES 字符串 加密解密以及SHA1 MD5加密
		
本文列举了 数据加密算法(Data Encryption Algorithm,DEA) 密码学中的高级加密标准(Advanced EncryptionStandard,AES)RSA公钥加密算法 ...
 - 使用X.509数字证书加密解密实务(三)-- 使用RSA证书结合对称加密技术加密长数据
		
一. 使用证书结合对称加密算法加.解密长数据 上一章节讨论了如何使用RSA证书加密数据,文中提到:“Dotnet的RSA实现有个特点,它必须要在明文中添加一些随机数,所以明文不能把128字节占满,实 ...
 - javascript之传输加密
		
为什么要使用javascript加密呢?服务端加密远远不够,客户端或者浏览器端也需要加密,以此保证传输信息过程的安全. 今天就我工作中说说这么几种加密算法及其对应的应用场景,如下所示: base64 ...
 - RSA算法原理与加密解密  求私钥等价求求模反元素  等价于分解出2个质数 (r*X+1)%[(p-1)(q-1)]=0
		
Rsapaper.pdf http://people.csail.mit.edu/rivest/Rsapaper.pdf [概述Abstract 1.将字符串按照双方约定的规则转化为小于n的正整数m, ...
 - C# RSA 无 长度限制 加密解密 示例
		
RSA 是一种非对称加密算法.由于算法特性,加密和解密过程用不同密钥,即公钥和私钥,而被广泛应用于数字证书的安全管理. 在具体应用中,公钥用加密而私钥用于解密,或 私钥用于数字签名而公钥用于签名验证. ...
 - 密码基础知识(2)以RSA为例说明加密、解密、签名、验签
		
密码基础知识(1)https://www.cnblogs.com/xdyixia/p/11528572.html 一.RSA加密简介 RSA加密是一种非对称加密.是由一对密钥来进行加解密的过程,分别称 ...
 - 转发:RSA实现JS前端加密,PHP后端解密
		
web前端,用户注册与登录,不能直接以明文形式提交用户密码,容易被截获,这时就引入RSA. 前端加密 需引入4个JS扩展文件,jsbn.js.prng4.js.rng.js和rsa.js. <h ...
 - java 加密工具类(MD5、RSA、AES等加密方式)
		
1.加密工具类encryption MD5加密 import org.apache.commons.codec.digest.DigestUtils; /** * MD5加密组件 * * @autho ...
 
随机推荐
- 在Centos中使用goaccess查看Nginx日志
			
在Nginx的配置文件中配置一下access日志: log_format access ‘$remote_addr – $remote_user [$time_local] “$request” ‘‘ ...
 - MSSQL-字符串分离与列记录合并成一行混合使用
			
一般我们在数据库的表字段存储字典Id,如果有多个的话一般是用,或分隔符分隔(12,14),列表显示的时候是显示字典名,那如果要在数据库将字典Id转成用户看得懂的字典名,该怎么办呢? 我们这时候可以结合 ...
 - Error:scalac: Error: org.jetbrains.jps.incremental.scala.remote.ServerException
			
Error:scalac: Error: org.jetbrains.jps.incremental.scala.remote.ServerException reason:JDK与Scala的版本不 ...
 - 2018 ICPC 徐州邀请赛 总结
			
Day 0 上午在高铁里面,很困但是睡不着…… 中午到矿大报道. 食堂饭菜不错,有西瓜,就是有点辣. 下午热身赛,D题队友想了个假算法……GG. 评测机摸底考试正常进行. 热身赛之后精疲力尽,赶到宾馆 ...
 - Spring Cloud底层原理(转载  石杉的架构笔记)
			
拜托!面试请不要再问我Spring Cloud底层原理 原创: 中华石杉 石杉的架构笔记 目录 一.业务场景介绍 二.Spring Cloud核心组件:Eureka 三.Spring Cloud核 ...
 - PMBOK及PMP考试精要
			
PROJECT MANAGEMENT KNOWLEDGE AREAS项目管理知识体系 2 PROJECT LIFE CYCLE / PROJECT MANAGEMENT PROCESS GROUPS项 ...
 - POJ 3041 Asteroids (二分图匹配)
			
[题目链接] http://poj.org/problem?id=3041 [题目大意] 一个棋盘上放着一些棋子 每次操作可以拿走一行上所有的棋子或者一列上所有的棋子 问几次操作可以拿完所有的棋子 [ ...
 - Java this关键字详解
			
this 关键字用来表示当前对象本身,或当前类的一个实例,通过 this 可以调用本对象的所有方法和属性.例如: public class Demo{ public int x = 10; publi ...
 - A Beginner’s Guide to the OUTPUT Clause in SQL Server
			
原文 A Beginner’s Guide to the OUTPUT Clause in SQL Server T-SQL supports the OUTPUT clause after the ...
 - Delphi  TClientDataset查找定位功能
			
if CDSUserFunc.Locate('mod_id;res_id', VarArrayOf([UserFunc.MOD_ID, UserFunc.RES_ID]), [loCaseInsens ...