目录

RSA Java 实践

RSA 是一种非对称加密算法。我们可以与任何人共享的公钥用于加密数据。只保留私钥,用于解密数据。

  • 1024 位证书,最大支持加密117字节,解密为128;
  • 2048 位证书,最大支持加密245字节,解密为256。

加密时支持的最大字节数:证书数/8 -11(如:2048位证书,支持的最大加密字节数:2048/8 - 11 = 245)

生成 RSA 密钥对

1
2
3
4
5
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair pair = generator.generateKeyPair();
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();

秘钥存储到文件中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
try (FileOutputStream fos = new FileOutputStream("public.key")) {
    fos.write(publicKey.getEncoded());
}

File publicKeyFile = new File("public.key");
byte[] publicKeyBytes = Files.readAllBytes(publicKeyFile.toPath());

KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
keyFactory.generatePublic(publicKeySpec);

加解密

加密

1
2
3
4
5
6
String secretMessage = "plain message";
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessage.getBytes(StandardCharsets.UTF_8));
// 使用 Base64 编码便于传输与保存
String encodedMessage = Base64.getEncoder().encodeToString(encryptedMessageBytes);

解密

1
2
3
4
5
6
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedMessageBytes = decryptCipher.doFinal(Base64.getDecoder().decode(encodedMessage));
String decryptedMessage = new String(decryptedMessageBytes, StandardCharsets.UTF_8);

assertEquals(secretMessage, decryptedMessage);

RsaUtil

  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
 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
@Slf4j
public class RsaUtil {

  private RsaUtil() {}

  /** 指定字符集 */
  private static final String UTF_8 = StandardCharsets.UTF_8.name();

  public static final String PROVIDER = "BC";

  public static final String KEY_ALGORITHM = "RSA";

  public static final String SIGN_TYPE_RSA2 = "SHA256WithRSA";

  public static final String PKCS12 = "PKCS12";
  public static final String X509 = "X.509";

  /** 貌似默认是RSA//PKCS1Padding,未验证 */
  public static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";

  /** RSA密钥长度必须是64的倍数,在512~65536之间。默认是1024 */
  public static final int KEY_SIZE = 1024;

  /** RSA最大加密明文大小 1024 位证书,最大支持加密117字节,解密为128 */
  private static final int MAX_ENCRYPT_BLOCK = 117;
  /** RSA最大解密密文大小 */
  private static final int MAX_DECRYPT_BLOCK = 128;

  static {
    Security.addProvider(new BouncyCastleProvider());
  }

  /**
   * 生成密钥对。注意这里是生成密钥对KeyPair,再由密钥对获取公私钥
   *
   * @return
   */
  public static Pair<String, String> generate1024KeyPair() {

    return generateKeyPair(KEY_SIZE);
  }

  public static Pair<String, String> generateKeyPair(int keySize) {
    try {
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
      keyPairGenerator.initialize(keySize, new SecureRandom());
      KeyPair keyPair = keyPairGenerator.generateKeyPair();

      RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
      RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

      return Pair.of(
          Base64.getEncoder().encodeToString(publicKey.getEncoded()),
          Base64.getEncoder().encodeToString(privateKey.getEncoded()));
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException(e);
    }
  }

  public static PublicKey publicKeyFromBase64(String publicKeyStr) {
    PublicKey publicKey;
    try {
      byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
      X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
      KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
      publicKey = factory.generatePublic(keySpec);
    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
      throw new RuntimeException(e);
    }
    return publicKey;
  }

  public static PrivateKey privateKeyFromBase64(String privateKeyStr) {

    try {
      byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
      PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
      KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
      return factory.generatePrivate(pkcs8EncodedKeySpec);
    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
      throw new RuntimeException(e);
    }
  }

  public static PublicKey publicKeyFromFile(String certPath) throws FileNotFoundException {
    return publicKey(new FileInputStream(certPath));
  }

  public static PublicKey publicKey(InputStream inputStream) {
    try (inputStream) {
      CertificateFactory certificateFactory = CertificateFactory.getInstance(X509, PROVIDER);
      Certificate certificate = certificateFactory.generateCertificate(inputStream);
      return certificate.getPublicKey();
    } catch (CertificateException | NoSuchProviderException | IOException e) {
      throw new RuntimeException(e);
    }
  }

  public static PrivateKey privateKeyFromFile(String storePath, String keyPass) {

    PrivateKey privateKey = null;
    try (InputStream in = new FileInputStream(storePath)) {
      KeyStore keyStore = KeyStore.getInstance(PKCS12, PROVIDER);

      keyStore.load(in, keyPass.toCharArray());

      Enumeration<String> enums = keyStore.aliases();
      while (enums.hasMoreElements()) {
        String keyAlias = enums.nextElement();
        if (keyStore.isKeyEntry(keyAlias)) {
          privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPass.toCharArray());
        }
      }

    } catch (KeyStoreException
        | NoSuchProviderException
        | NoSuchAlgorithmException
        | CertificateException
        | IOException
        | UnrecoverableKeyException e) {
      throw new RuntimeException(e);
    }

    return privateKey;
  }

  /**
   * 公钥加密方法 分段加密
   *
   * @param publicKeyText Base64 编码公钥
   * @param source 源数据
   * @return Base64 编码密文
   * @throws Exception
   */
  public static String encryptSegment(String publicKeyText, String source) {
    PublicKey publicKey = publicKeyFromBase64(publicKeyText);
    try {
      return Base64.getEncoder().encodeToString(encryptSegment(publicKey, source.getBytes(UTF_8)));
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }

  public static byte[] encryptSegment(Key publicKey, byte[] data) {
    try {
      Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);

      /** 执行分组加密操作 */
      return segment(cipher, data, MAX_ENCRYPT_BLOCK);
    } catch (NoSuchPaddingException
        | IllegalBlockSizeException
        | NoSuchAlgorithmException
        | IOException
        | BadPaddingException
        | NoSuchProviderException
        | InvalidKeyException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 私钥解密算法 分段加密
   *
   * @param privateKeyInBase64 Base64 编码秘钥
   * @param cryptoSrc 密文
   * @return 明文
   * @throws Exception
   */
  public static String decryptSegment(String privateKeyInBase64, String cryptoSrc) {
    PrivateKey privateKey = privateKeyFromBase64(privateKeyInBase64);
    try {
      return new String(decryptSegment(privateKey, Base64.getDecoder().decode(cryptoSrc)), UTF_8);
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }

  public static byte[] decryptSegment(PrivateKey privateKey, byte[] encryptedData) {
    try {
      Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
      cipher.init(Cipher.DECRYPT_MODE, privateKey);
      return segment(cipher, encryptedData, MAX_DECRYPT_BLOCK);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  private static byte[] segment(Cipher cipher, byte[] bytes, int maxBlock)
      throws IOException, IllegalBlockSizeException, BadPaddingException {

    byte[] decryptedData;
    /** 执行解密操作 */
    int inputLen = bytes.length;

    int offSet = 0;
    byte[] cache;
    int i = 0;
    try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
      // 对数据分段加密/解密
      while (inputLen - offSet > 0) {
        if (inputLen - offSet > maxBlock) {
          cache = cipher.doFinal(bytes, offSet, maxBlock);
        } else {
          cache = cipher.doFinal(bytes, offSet, inputLen - offSet);
        }
        out.write(cache, 0, cache.length);
        i++;
        offSet = i * maxBlock;
      }
      decryptedData = out.toByteArray();
    }

    return decryptedData;
  }

  public static String signRsa2(String privateKeyStr, String content) {
    return sign(content, privateKeyStr, SIGN_TYPE_RSA2);
  }

  public static boolean verifyRsa2(String publicKeyStr, String content, String sign) {
    return verify(publicKeyStr, sign, content, SIGN_TYPE_RSA2);
  }

  /**
   * 私钥签名
   *
   * @param content 需要签名的数据
   * @param privateKeyStr 私钥 BASE64编码
   * @param signType 签名算法
   * @return 签名结果 Base64 编码
   */
  public static String sign(String content, String privateKeyStr, String signType) {
    try {
      return Base64.getEncoder()
          .encodeToString(
              sign(content.getBytes(UTF_8), privateKeyFromBase64(privateKeyStr), signType));
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 私钥签名
   *
   * @param content 需要签名的数据
   * @param privateKey 私钥
   * @param signType 签名算法
   * @return 签名结果
   */
  private static byte[] sign(byte[] content, PrivateKey privateKey, String signType) {
    Signature signature;
    try {
      signature = Signature.getInstance(signType);
      signature.initSign(privateKey);
      signature.update(content);
      return signature.sign();
    } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 验证签名
   *
   * @param publicKeyStr Base64 编码公钥
   * @param sign Base64 编码签名数据
   * @param content 明文
   * @param signType 签名算法
   * @return
   */
  public static boolean verify(String publicKeyStr, String sign, String content, String signType) {
    return verify(
        publicKeyFromBase64(publicKeyStr), Base64.getDecoder().decode(sign), content, signType);
  }

  private static boolean verify(
      PublicKey publicKey, byte[] signatureBytes, String content, String signType) {
    try {
      Signature signature = Signature.getInstance(signType);
      signature.initVerify(publicKey);
      signature.update(content.getBytes(UTF_8));
      return signature.verify(signatureBytes);
    } catch (NoSuchAlgorithmException
        | InvalidKeyException
        | SignatureException
        | UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }
}

Java keystore

JAVA有一个keystore用来存放私钥和证书,该文件是伴随JDK默认存在的,路径默认是 /lib/security/cacerts,默认密码是changeit,实际上空密码也可以直接访问。

1
2
3
4
/usr/libexec/java_home -V
cd /Library/Java/JavaVirtualMachines/jdk-17.0.1.jdk/Contents/Home
cd lib/security
keytool -list -rfc -keystore cacerts

SSL 证书格式

Base64 (ASCII)编码的 文本格式 :

PEM

  • .pem
  • .crt
  • .ca-banadle

PKCS#7

  • p7b
  • p7s

Binary 二进制文件 :

DER

  • .der
  • .cer

PKCS#12

  • .pfx
  • .p12

证书文件的后缀并不能作为证书是哪一种编码的判断依据,以常见的 .cer 和 .crt 后缀的证书为例,这个证书文件可能是PEM编码的,也有可能是DER编码的,你可以用记事本打开这个文件,如果是以 —–BEGIN 开头的,那么就是PEM编码,如果不是那可能是DER编码的

  • 证书(Certificate) - *.cer *.crt
  • 私钥(Private Key) - *.key
  • 证书签名请求(Certificate signing request) - *.csr

至于pem和der,是编码方式,以上三类均可以使用这两种编码方式

PEM:- Privacy Enhanced Mail,打开看文本格式,以”—–BEGIN…”开头, “—–END…”结尾,内容是BASE64编码.上述一般证书文件 ,中间证书和根证书,证书文件很多都是pem格式的。Apache和NIgnix服务器偏向于使用这种编码格式。

DER:这种格式也是常见的证书格式,跟pem类似,中间证书和根证书,证书文件很多都是DER的,Java和Windows服务器偏向于使用这种编码格式。

JKS :jks是Java密钥库(KeyStore)比较常见的一种格式。一般可用通过cer 或者pem 格式的证书以及私钥的进行转化为jks格式,有密码保护。所以它是带有私钥的证书文件,一般用户tomcat环境的安装。

PFX:pfx格式的证书也是由cer 或者pem格式的证书文件以及私钥转化而来,所以该证书文件也是带有私钥的证书文件,一般用于iis 环境的证书安装。

PKCS 全称是 Public-Key Cryptography Standards ,是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准,PKCS 目前共发布过 15 个标准。 常用的有:

  • KCS#7 Cryptographic Message Syntax Standard
  • PKCS#10 Certification Request Standard
  • PKCS#12 Personal Information Exchange Syntax Standard

X.509 是常见通用的证书格式。所有的证书都符合为Public Key Infrastructure (PKI) 制定的 ITU-T X509 国际标准。

  • PKCS#7 常用的后缀是: .P7B .P7C .SPC

  • PKCS#12 常用的后缀有: .P12 .PFX

  • X.509 DER 编码(ASCII)的后缀是: .DER .CER .CRT

  • X.509 PAM 编码(Base64)的后缀是: .PEM .CER .CRT

  • .cer/.crt是用于存放证书,它是2进制形式存放的,不含私钥。

  • .pem跟crt/cer的区别是它以Ascii来表示。

  • pfx/p12用于存放个人证书/私钥,他通常包含保护密码,2进制方式

证书的格式可以相互转换,openssl 先生成私钥,然后再生成证书签名请求(Certificate signing request),最后通过私钥与证书签名请求生成公钥。

1
2
openssl req -newkey rsa:2048 -nodes -out test.csr -keyout test.key
openssl x509 -req -days 365 -in test.csr -signkey test.key -out test.crt