在JEE项目中实施SSL双向认证

一、为什么要实施双向认证(Why)


双向认证一般使用在B2B系统或企业内部系统中,目的就是阻止无关人员访问系统,哪怕就一个登录页面也不行。只有系统管理员给你发放了证书,你才能访问到该系统。

二、准备工作(Getting ready)

1.
你系统中要有JDK

2.
你要有一个Servlet容器,这里使用tomcat7

3. 下载BouncyCastle安全工具包,如果你使用Maven管理依赖,请加入下面的依赖项





1

2

3

4

5

6

7

8

9

10

<dependency>                                 

    <groupId>org.bouncycastle</groupId>      

    <artifactId>bcprov-jdk15on</artifactId>  

    <version>1.50</version>

</dependency>                                

<dependency>                                 

    <groupId>org.bouncycastle</groupId>      

    <artifactId>bcpkix-jdk15on</artifactId>  

    <version>1.50</version>

</dependency>

三、实施(How to do it)

1. 生成一个自签名的CA证书

使用JDK自带的keytool工具生成CA证书





1

2

3

keytool -genkeypair -alias
"Daojoo.com Co.Ltd. Root CA" -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -dname "CN=Daojoo.com Co.Ltd. Root CA,OU=CA Center,O=Daojoo.com Co.Ltd.,C=CN"
-keypass www.daojoo.com -validity 36135 -storetype PKCS12 -keystore "E:\Daojoo.com Co.Ltd. Root CA.pfx"
-storepass www.daojoo.com

keytool -exportcert -alias
"Daojoo.com Co.Ltd. Root CA" -file
"E:\Daojoo.com Co.Ltd. Root CA.cer"
-storetype PKCS12 -keystore "E:\Daojoo.com Co.Ltd. Root CA.pfx"
-storepass www.daojoo.com -rfc

第一条命令生成pfx格式的keystore,包含私钥;第二条命令导出CA证书。

编程生成CA证书





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

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

// CA 别名

String caAlias = "Daojoo.com Co.Ltd. Root CA";

// CA 证书保护密码

char[] password = "www.daojoo.com".toCharArray();

X500NameBuilder x500NameBuilder = new
X500NameBuilder(BCStrictStyle.INSTANCE);

x500NameBuilder.addRDN(BCStyle.C, "CN");

x500NameBuilder.addRDN(BCStyle.O, "Daojoo.com Co.Ltd.");

x500NameBuilder.addRDN(BCStyle.OU, "CA Center");

x500NameBuilder.addRDN(BCStyle.CN, "Daojoo.com Co.Ltd. Root CA");

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");

keyPairGenerator.initialize(2048);

KeyPair keyPair = keyPairGenerator.generateKeyPair();

// 证书签发者

X500Name issuer = x500NameBuilder.build();

// 证书序列号

BigInteger serial = BigInteger.valueOf(0);

// 证书开始有效期

Date notBefore = new
Date();

// 证书结束有效期,99年

Date notAfter = new
Date(notBefore.getTime() + 1000L * 3600
* 24
* 365
* 99);

// 证书使用者

X500Name subject = issuer;

// CA证书公钥

PublicKey caPublicKey = keyPair.getPublic();

// CA证书私钥

PrivateKey caPrivateKey = keyPair.getPrivate();

JcaX509v3CertificateBuilder x509v3CertificateBuilder = new
JcaX509v3CertificateBuilder(

        issuer,

        serial,

        notBefore,

        notAfter,

        subject,

        caPublicKey

);

JcaX509ExtensionUtils x509ExtensionUtils = new
JcaX509ExtensionUtils();

// 证书扩展 基本约束 关键 可签发证书

x509v3CertificateBuilder.addExtension(Extension.basicConstraints, true

        , new
BasicConstraints(true));

// 证书扩展 签发者密钥标识 非关键

x509v3CertificateBuilder.addExtension(Extension.authorityKeyIdentifier, false

        , x509ExtensionUtils.createAuthorityKeyIdentifier(caPublicKey));

// 证书扩展 使用者密钥标识 非关键

x509v3CertificateBuilder.addExtension(Extension.subjectKeyIdentifier, false

        , x509ExtensionUtils.createSubjectKeyIdentifier(caPublicKey));

// 证书扩展 密钥用法 关键 (用于吊销列表签名、证书签名)

x509v3CertificateBuilder.addExtension(Extension.keyUsage, true, new
KeyUsage(

        KeyUsage.cRLSign | KeyUsage.keyCertSign));

ContentSigner signer = new
JcaContentSignerBuilder("SHA1withRSA").build(caPrivateKey);

X509CertificateHolder x509CertificateHolder = x509v3CertificateBuilder.build(signer);

JcaX509CertificateConverter certificateConverter = new
JcaX509CertificateConverter();

Certificate caCertificate = certificateConverter.getCertificate(x509CertificateHolder);

// 导出根证书

OutputStream caOs = new
FileOutputStream(caAlias + ".cer");

caOs.write(caCertificate.getEncoded());

caOs.close();

// 导出为PKCS12

// 将证书、私钥及信任链保存到PKCS12格式的文件中

OutputStream os = new
FileOutputStream(caAlias + ".pfx");

KeyStore eeKeyStore = KeyStore.getInstance("PKCS12", "BC");

eeKeyStore.load(null, null);

eeKeyStore.setCertificateEntry(caAlias, caCertificate);

eeKeyStore.setKeyEntry(caAlias, caPrivateKey, password, new
Certificate[]{caCertificate});

eeKeyStore.store(os, password);

2. 生成服务器证书





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

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

// CA 别名

String caAlias = "Daojoo.com Co.Ltd. Root CA";

// CA 证书保护密码

char[] caPassword = "www.daojoo.com".toCharArray();

// 使用者别名

String eeAlias = "*.daojoo.com";

// 使用者密码

char[] eePassword = "daojoo.com".toCharArray();

KeyStore caKeyStore = KeyStore.getInstance("PKCS12", "BC");

caKeyStore.load(new
FileInputStream(caAlias + ".pfx"), caPassword);

Enumeration<String> aliases = caKeyStore.aliases();

// CA 证书

X509Certificate caCertificate = null;

// CA 私钥

PrivateKey caPrivateKey = null;

while
(aliases.hasMoreElements()) {

    String alias = aliases.nextElement();

    if
(caKeyStore.isKeyEntry(alias)) {

        caCertificate = (X509Certificate) caKeyStore.getCertificate(alias);

        caPrivateKey = (PrivateKey) caKeyStore.getKey(alias, caPassword);

    }

}

X500NameBuilder x500NameBuilder = new
X500NameBuilder();

x500NameBuilder.addRDN(BCStyle.C, "CN");

x500NameBuilder.addRDN(BCStyle.O, "Daojoo.com Co.Ltd.");

x500NameBuilder.addRDN(BCStyle.OU, "Web Servers");

x500NameBuilder.addRDN(BCStyle.CN, eeAlias);

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");

keyPairGenerator.initialize(2048);

KeyPair keyPair = keyPairGenerator.generateKeyPair();

// 证书使用者

X500Name subject = x500NameBuilder.build();

// 证书签发者

X500Name issuer = X500Name.getInstance(caCertificate.getIssuerX500Principal().getEncoded());

// 证书开始有效期

Date notBefore = new
Date();

// 证书结束有效期  以有效期为1年计算

Date notAfter = new
Date(notBefore.getTime() + 1000L * 3600
* 24
* 365);

// 证书序列号

BigInteger serial = BigInteger.valueOf(1);

// 证书公钥

PublicKey eePublicKey = keyPair.getPublic();

// 证书私钥

PrivateKey eePrivateKey = keyPair.getPrivate();

X509v3CertificateBuilder x509v3CertificateBuilder = new
JcaX509v3CertificateBuilder(

        caCertificate,

        serial,

        notBefore,

        notAfter,

        subject,

        eePublicKey

);

JcaX509ExtensionUtils x509ExtensionUtils = new
JcaX509ExtensionUtils();

// 证书扩展 基本约束 关键 不签发证书

x509v3CertificateBuilder.addExtension(Extension.basicConstraints, true

        , new
BasicConstraints(false));

// 证书扩展 签发者密钥标识 非关键

x509v3CertificateBuilder.addExtension(Extension.authorityKeyIdentifier, false

        , x509ExtensionUtils.createAuthorityKeyIdentifier(caCertificate));

// 证书扩展 使用者密钥标识 非关键

x509v3CertificateBuilder.addExtension(Extension.subjectKeyIdentifier, false

        , x509ExtensionUtils.createSubjectKeyIdentifier(keyPair.getPublic()));

// 证书扩展 密钥用法 关键 (数字签名、数据加密、密钥加密)

x509v3CertificateBuilder.addExtension(Extension.keyUsage, true, new
KeyUsage(

        KeyUsage.digitalSignature | KeyUsage.dataEncipherment | KeyUsage.keyEncipherment));

// 证书扩展 增强密钥用法 关键 (客户端认证、服务器认证)

x509v3CertificateBuilder.addExtension(Extension.extendedKeyUsage, true

        , new
ExtendedKeyUsage(new
KeyPurposeId[]{KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth}));

// 为证书签名

ContentSigner signer = new
JcaContentSignerBuilder("SHA1withRSA").build(caPrivateKey);

X509CertificateHolder x509CertificateHolder = x509v3CertificateBuilder.build(signer);

JcaX509CertificateConverter x509CertificateConverter = new
JcaX509CertificateConverter();

// 得到使用者证书

Certificate endEntityCertificate = x509CertificateConverter.getCertificate(x509CertificateHolder);

// 导出根证书

KeyStore endEntityKeyStore = KeyStore.getInstance("JKS");

endEntityKeyStore.load(null, null);

endEntityKeyStore.setCertificateEntry(caAlias, caCertificate);

// 证书链,最终证书在前、CA证书在后

Certificate[] chain = new
Certificate[]{endEntityCertificate, caCertificate};

endEntityKeyStore.setKeyEntry(eeAlias, keyPair.getPrivate(), eePassword, chain);

endEntityKeyStore.store(new
FileOutputStream(eeAlias.substring(2, eeAlias.length()) + ".jks"), eePassword);

3.
配置tomcat

将生成的服务器证书daojoo.com.jks拷贝到tomcat的conf目录下

在server.xml中添加一个Connector





1

2

3

4

5

6

7

8

9

10

11

12

13

<Connector
port="443"
protocol="org.apache.coyote.http11.Http11Protocol"

               connectionTimeout="20000"

   SSLEnabled="true"

   scheme="https"

   secure="true"

   clientAuth="true"

   sslProtocol="TLS"

   keystoreFile="conf/daojoo.com.jks"

   keystorePass="daojoo.com"

   truststoreFile="conf/daojoo.com.jks"

   truststorePass="daojoo.com"

       URIEncoding="UTF-8"
/>

4. 生成客户端证书





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

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

// CA 别名

String caAlias = "Daojoo.com Co.Ltd. Root CA";

// CA 证书保护密码

char[] caPassword = "www.daojoo.com".toCharArray();

// 使用者别名

String eeAlias = "daojoo";

// 使用者密码

char[] eePassword = "daojoo".toCharArray();

KeyStore caKeyStore = KeyStore.getInstance("PKCS12", "BC");

caKeyStore.load(new
FileInputStream(caAlias + ".pfx"), caPassword);

Enumeration<String> aliases = caKeyStore.aliases();

// CA 证书

X509Certificate caCertificate = null;

// CA 私钥

PrivateKey caPrivateKey = null;

while
(aliases.hasMoreElements()) {

    String alias = aliases.nextElement();

    if
(caKeyStore.isKeyEntry(alias)) {

        caCertificate = (X509Certificate) caKeyStore.getCertificate(alias);

        caPrivateKey = (PrivateKey) caKeyStore.getKey(alias, caPassword);

    }

}

X500NameBuilder x500NameBuilder = new
X500NameBuilder();

x500NameBuilder.addRDN(BCStyle.C, "CN");

x500NameBuilder.addRDN(BCStyle.ST, "Beijing");

x500NameBuilder.addRDN(BCStyle.L, "Beijing");

x500NameBuilder.addRDN(BCStyle.E, "[email protected]");

x500NameBuilder.addRDN(BCStyle.O, "Daojoo.com Co.Ltd.");

x500NameBuilder.addRDN(BCStyle.OU, "Tech");

x500NameBuilder.addRDN(BCStyle.CN, "daojoo");

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");

keyPairGenerator.initialize(2048);

KeyPair keyPair = keyPairGenerator.generateKeyPair();

// 证书使用者

X500Name subject = x500NameBuilder.build();

// 证书签发者

X500Name issuer = X500Name.getInstance(caCertificate.getIssuerX500Principal().getEncoded());

// 证书开始有效期

Date notBefore = new
Date();

// 证书结束有效期  以有效期为1年计算

Date notAfter = new
Date(notBefore.getTime() + 1000L * 3600
* 24
* 365);

// 证书序列号

BigInteger serial = BigInteger.valueOf(2);

// 证书公钥

PublicKey eePublicKey = keyPair.getPublic();

// 证书私钥

PrivateKey eePrivateKey = keyPair.getPrivate();

X509v3CertificateBuilder x509v3CertificateBuilder = new
JcaX509v3CertificateBuilder(

        caCertificate,

        serial,

        notBefore,

        notAfter,

        subject,

        eePublicKey

);

JcaX509ExtensionUtils x509ExtensionUtils = new
JcaX509ExtensionUtils();

// 证书扩展 基本约束 关键 不签发证书

x509v3CertificateBuilder.addExtension(Extension.basicConstraints, true

        , new
BasicConstraints(false));

// 证书扩展 签发者密钥标识 非关键

x509v3CertificateBuilder.addExtension(Extension.authorityKeyIdentifier, false

        , x509ExtensionUtils.createAuthorityKeyIdentifier(caCertificate));

// 证书扩展 使用者密钥标识 非关键

x509v3CertificateBuilder.addExtension(Extension.subjectKeyIdentifier, false

        , x509ExtensionUtils.createSubjectKeyIdentifier(keyPair.getPublic()));

// 证书扩展 密钥用法 关键 (数字签名、数据加密、密钥加密)

x509v3CertificateBuilder.addExtension(Extension.keyUsage, true, new
KeyUsage(

        KeyUsage.digitalSignature | KeyUsage.dataEncipherment | KeyUsage.keyEncipherment));

// 证书扩展 增强密钥用法 关键 (客户端认证、服务器认证)

x509v3CertificateBuilder.addExtension(Extension.extendedKeyUsage, true

        , new
ExtendedKeyUsage(new
KeyPurposeId[]{KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth}));

// 为证书签名

ContentSigner signer = new
JcaContentSignerBuilder("SHA1withRSA").build(caPrivateKey);

X509CertificateHolder x509CertificateHolder = x509v3CertificateBuilder.build(signer);

JcaX509CertificateConverter x509CertificateConverter = new
JcaX509CertificateConverter();

// 得到使用者证书

Certificate endEntityCertificate = x509CertificateConverter.getCertificate(x509CertificateHolder);

// 导出根证书

KeyStore endEntityKeyStore = KeyStore.getInstance("PKCS12", "BC");

endEntityKeyStore.load(null, null);

endEntityKeyStore.setCertificateEntry(caAlias, caCertificate);

// 证书链,最终证书在前、CA证书在后

Certificate[] chain = new
Certificate[]{endEntityCertificate, caCertificate};

endEntityKeyStore.setKeyEntry(eeAlias, keyPair.getPrivate(), eePassword, chain);

endEntityKeyStore.store(new
FileOutputStream(eeAlias + ".pfx"), eePassword);

四、如何工作(How it works)

1. 添加BCProvider





1

2

3

static
{

    Security.addProvider(new
BouncyCastleProvider());

}

2. 解除BCProvider密钥长度限制

下载对应版本jdk的jce不限密钥长度策略文件

Java
Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files
7

Java
Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files
6

解压后替换掉以下目录中的同名文件

$JRE_HOME/lib/security

$JAVA_HOME/jre/lib/security

3. 同一CA颁发的证书序列号一定不能相同。如果在项目中,证书序列号没有业务含义,则可使用uuid,如:





1

2

// 证书序列号

BigInteger serial = new
BigInteger(UUID.randomUUID().toString().replaceAll("-", ""), 16);

4. 当使用X500NameBuilder构建DN时,如果想控制生成的证书中的DN字段顺序,注意最终生成的DN与构建顺序相反

构建时DN字段顺序如下:





1

2

3

4

5

6

7

8

X500NameBuilder x500NameBuilder = new
X500NameBuilder();

x500NameBuilder.addRDN(BCStyle.C, "CN");

x500NameBuilder.addRDN(BCStyle.ST, "Beijing");

x500NameBuilder.addRDN(BCStyle.L, "Beijing");

x500NameBuilder.addRDN(BCStyle.E, "[email protected]");

x500NameBuilder.addRDN(BCStyle.O, "Daojoo.com Co.Ltd.");

x500NameBuilder.addRDN(BCStyle.OU, "Tech");

x500NameBuilder.addRDN(BCStyle.CN, "daojoo");

证书中展现的DN字段顺序如下:





1

2

3

4

5

6

7

CN = daojoo

OU = Tech

O = Daojoo.com Co.Ltd.

E = [email protected]

L = Beijing

S = Beijing

C = CN

5. 使用keytool生成证书时,我没找到控制证书扩展项的参数。

五、参考资料(Reference)

?1. RFC2459

2. 公钥基础设施(PKI)实现和管理电子安全 清华大学出版社

3. The
Cryptoworkshop Guide to Java Cryptography and the Bouncy Castle
APIs

来自为知笔记(Wiz)

时间: 2024-10-29 08:44:29

在JEE项目中实施SSL双向认证的相关文章

tomcat配置SSL双向认证

一.SSL简单介绍 SSL(Secure Sockets Layer 安全套接层)就是一种协议(规范),用于保障客户端和服务器端通信的安全,以免通信时传输的信息被窃取或者修改. 怎样保障数据传输安全?  客户端和服务器端在进行握手(客户端和服务器建立连接和交换参数的过程称之为握手)时会产生一个“对话密钥”(session key),用来加密接下来的数据传输,解密时也是用的这个“对话密钥”,而这个“对话密钥”只有客户端和服务器端知道.也就是说只要这个“对话密钥”不被破解,就能保证安全. 2. 客户

Nginx、SSL双向认证、PHP、SOAP、Webservice、https

本文是1:1模式,N:1模式请参见新的一篇博客<SSL双向认证(高清版)> ----------------------------------------------------- 我是分割线 --------------------------------------------------------- 标题太长了不知道该怎么起,索性就把keyword列出来吧~ WebService的WS-*搞了一天没搞定,看样子PHP应该是彻底抛弃SOAP协议了,google翻烂了也没找到什么靠谱的解

SSL双向认证(高清版)

介绍了SSL双向认证的一些基本问题,以及使用Nginx+PHP基于它搭建https的Webservice. 之前的方式只是实现1:1的模式,昨天同事继续实现了n:1的模式,这里我再整理记录下. 由于nginx的ssl_client_certificate参数只能指定一个客户端公钥,如果增加一个客户端进行通信就要重新配一个server. n:1的模式是通过CA的级联证书模式实现的,首先自己生成一套CA根级证书,再借助其生成二级证书作为client证书. 此时client私钥签名不仅可以通过对应的c

SSL双向认证和SSL单向认证的流程和区别

refs: SSL双向认证和SSL单向认证的区别https://www.jianshu.com/p/fb5fe0165ef2 图解 https 单向认证和双向认证!https://cloud.tencent.com/developer/news/233610 SSL/TLS 双向认证(一) -- SSL/TLS工作原理https://blog.csdn.net/wuliganggang/article/details/78428866 双向认证 SSL 协议要求服务器和用户双方都有证书.单向认证

【密码学】ssl双向认证和单向认证原理

有朋友在搞一个项目,周末有聊到一些安全性的东西,很自然会想起https,但https究竟如何实施,其原理又是什么? 基于ssl,一般的应用都是单向认证,如果应用场景要求对客户来源做验证也可以实现成双向认证. 网上google一下: 为了便于更好的认识和理解 SSL 协议,这里着重介绍 SSL 协议的握手协议.SSL 协议既用到了公钥加密技术又用到了对称加密技术,对称加密技术虽然比公钥加密技术的速度快,可是公钥加密技术提供了更好的身份认证技术.SSL 的握手协议非常有效的让客户和服务器之间完成相互

SSL双向认证

之前的方式只是实现1:1的模式,昨天同事继续实现了n:1的模式,这里我再整理记录下. 由于nginx的ssl_client_certificate参数只能指定一个客户端公钥,如果增加一个客户端进行通信就要重新配一个server. n:1的模式是通过CA的级联证书模式实现的,首先自己生成一套CA根级证书,再借助其生成二级证书作为client证书. 此时client私钥签名不仅可以通过对应的client公钥验证,还可通过根证书的公钥进行验证. 看到这里应该豁然开朗了吧,下面简单介绍下具体怎么操作:

webservice ssl双向认证配置

1.在tomcat中安装axis2插件 2.生成证书,用jdk自带的keytool 服务端 keytool -genkey -alias Server -dname "CN=192.168.10.100, OU=JH, O=JH, L=HangZhou, S=ZheJiang, C=CN" -keystore server.keystore -keyalg RSA keytool -export -alias Server -file server.cer -storepass 123

(备忘)Nginx配置客户端SSL双向认证

对于 NGINX 的 HTTPS 配置,通常情况下我们只需要实现服务端认证就行,因为浏览器内置了一些受信任的证书颁发机构(CA),服务器端只需要拿到这些机构颁发的证书并配置好,浏览器会自己校验证书的可用性并通过 SSL 进行通讯加密. 但特殊情况下我们也需要对客户端进行验证,只有受信任的客户端才能使用服务接口,此时我们就需要启用双向认证来达到这个目的,只有 当客户端请求带了可用的证书才能调通服务端接口 . CA 与自签名 CA 是权威机构才能做的,并且如果该机构达不到安全标准就会被浏览器厂商"封

SSL双向认证以及证书的制作和使用

客户端认证服务器: 正规的做法是:到国际知名的证书颁发机构,如VeriSign申请一本服务器证书,比如支付宝的首页,点击小锁的图标,可以看到支付宝是通过VeriSign认证颁发的服务器证书: 我们用的操作系统(windows, linux, unix ,android, ios等)都预置了很多信任的根证书,比如我的windows中就包含VeriSign的根证书,那么浏览器访问服务器比如支付宝www.alipay.com时,SSL协议握手时服务器就会把它的服务器证书发给用户浏览器,而这本服务器证书