美好的一天! 我有一个有效的 JavaScript 脚本,可以从服务器获取令牌。我需要在 Java 中实现类似的东西。本质是我们从服务器接收到一个“-----BEGIN PUBLIC KEY.....”形式的字符串,我们将其转换为RSAAublicKey并用它用RSAOAEP384加密一个密码并发送给服务器,但我的实现(使用 X509EncodedKeySpec)从服务器获取 system.error 响应。 我怀疑一切都是因为我尝试将字符串转换为 PublicKey 的方式,模数总是相同的数字,但它与脚本中获得的模数不同,指数始终是 65537,它是那里和这里都是一样的,但据我了解,这是标准的,在Java中将字符串转换为公钥可能有一些特殊之处?
工作中的JS:
const pubKey = KEYUTIL.getKey(pubkeyResp.pubKey)
const encodedPassword = encodeURIComponent(password);
let encryptedPassword = "";
for (let i = 0; i < encodedPassword.length / 270; i++) {
let currntValue = encodedPassword.substr(i * 270, 270);
let encryptValueCurrent = KJUR.crypto.Cipher.encrypt(currntValue, pubKey, "RSAOAEP384");
encryptedPassword = encryptedPassword == "" ? "" : encryptedPassword + "00000001";
encryptedPassword = encryptedPassword + hextob64(encryptValueCurrent);
}
我的JAVA代码:
public static RSAPublicKey createRsaPublicKey(String pubKey) {
PemObject pemObject;
try (PemReader pemReader = new PemReader(new StringReader(pubKey))) {
pemObject = pemReader.readPemObject();
}
byte[] pemBytes = pemObject.getContent();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pemBytes);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}
public static String encryptPubKeyWithPassword(String pubKey, String password, String version) {
try {
String encodedPassword = URLEncoder.encode(password, StandardCharsets.UTF_8);
RSAPublicKey publicKey = createRsaPublicKey(pubKey);
StringBuilder encryptedPasswordBuilder = new StringBuilder();
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-384AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
for (int i = 0; i < encodedPassword.length(); i += 270) {
String currentValue = encodedPassword.substring(i, Math.min(i + 270, encodedPassword.length()));
if (encryptedPasswordBuilder.length() > 0) encryptedPasswordBuilder.append("00000001");
encryptedPasswordBuilder.append(Base64.getEncoder().encodeToString(cipher.doFinal(currentValue.getBytes(StandardCharsets.UTF_8))));
}
return encryptedPasswordBuilder + version;
}
公钥:
-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArjLfZpVakwA2ETwGjQLVA+5e5QYDNSu2V2fWPCveuxMF72EcKB/Mw+cx6Xe/AFwdysu3DBefH8FoRsL4HmCqilrdbTrGqiLvZ0ULd7vh8Y7bBT396hfro7CkS+AtauF9MxGLXdRN1ALpNoD2MDYKy/4WT7BKy0EAt2BxdtN841gtDpYTsC0Qv9+vEcI9PG2NI7nWZORAOsktZA1xpsMBTluDizsVcjAr0hKBlDpPhs+UWeugaiq7bQm5GblSvaYHsiRAJOse5bba5gRespgwFWGBiv/EdDSQ8WLB0RSfj7zWOQl6XlxPXkzKGtbXW7kP41Q5CtJmlRXaEaAzKRygUyxNYbdtAm/P0aY3wS1b/soNqTBnirFuUZlo8VVD2Cy/xPs/it+GVnhtztrt1VsgY0K/XI2YmtJyQc1VJsgIHtJkUExwp5s9bn96gD/UxvvDS5kW4IbKfTMsWdoBcDzkCYXgNvXN4DfuzKsu7Z4Q6V0iqXOSUnioBN2CsHPjgj3vAgMBAAE=\n-----END PUBLIC KEY-----
来自 JS 的 RSAKey:
RSAKey {
n: BigInteger {
'0': 58867183,
'1': 137037630,
'2': 145229021,
'3': 121185575,
'4': 157098665,
'5': 249159950,
'6': 248294190,
'7': 215876478,
'8': 98580213,
'9': 63848600,
'10': 165282160,
'11': 131281605,
'12': 115377866,
'13': 204781969,
'14': 265602811,
'15': 133670915,
'16': 127614318,
'17': 84199178,
'18': 136237668,
'19': 89287808,
'20': 41042381,
'21': 148474285,
'22': 54706012,
'23': 223719942,
'24': 231660269,
'25': 140863366,
'26': 188713695,
'27': 46922831,
'28': 22365144,
'29': 85563023,
'30': 126529902,
'31': 14324486,
'32': 224132810,
'33': 174291986,
'34': 40882129,
'35': 102463184,
'36': 5450829,
'37': 53645770,
'38': 98177440,
'39': 220621137,
'40': 55851274,
'41': 96178430,
'42': 169531095,
'43': 83223756,
'44': 159014492,
'45': 198009744,
'46': 18128783,
'47': 253111325,
'48': 74724496,
'49': 135835644,
'50': 137368929,
'51': 4582185,
'52': 95869670,
'53': 38711790,
'54': 129115200,
'55': 86760032,
'56': 163125689,
'57': 44807888,
'58': 166436970,
'59': 141359429,
'60': 26491471,
'61': 45949224,
'62': 185954864,
'63': 95959219,
'64': 113443150,
'65': 104912666,
'66': 3852589,
'67': 224808516,
'68': 227353529,
'69': 203674566,
'70': 266317585,
'71': 184733963,
'72': 219059731,
'73': 130954626,
'74': 7435987,
'75': 68160374,
'76': 263211723,
'77': 213901668,
'78': 103822858,
'79': 244541455,
'80': 72209410,
'81': 18396637,
'82': 182549811,
'83': 79561430,
'84': 195276964,
'85': 266248574,
'86': 249234749,
'87': 197009176,
'88': 121965431,
'89': 178401014,
'90': 225262278,
'91': 178824621,
'92': 49815136,
'93': 202802284,
'94': 202874655,
'95': 212646768,
'96': 251681821,
'97': 52336507,
'98': 265077735,
'99': 101827201,
'100': 185796079,
'101': 63094251,
'102': 106391510,
'103': 3363515,
'104': 241100038,
'105': 2969662,
'106': 20711053,
'107': 154141537,
'108': 258381146,
'109': 713517,
t: 110,
s: 0
},
e: 65537,
d: null,
p: null,
q: null,
dmp1: null,
dmq1: null,
coeff: null,
isPublic: true,
isPrivate: false
}
我的RSA公钥:
2024-04-09T18:08:10.252+03:00 INFO 2876 --- [test-token] [nio-8080-exec-5] c.e.test-token.ClientUtil : Sun RSA public key, 3072 bits
params: null
modulus: 3953226311687758595392111994184029919105572353883850175466519164541873393823923732214781564401042342584241029257336199908139796366497541069327180034755296728788397456817216176094110417813365270437034224139108683316529614756336144052543998303152918929941819677301941145250628636498495113956820927519270970709149908767271627169825801967465740997006710347979917608611090978116377701630380908449017037401297495565187968809576663765854765455615940054260715071874403034550696249783288415681844605115585869191283022766347390049238750984142454221808268833091744962848703466396221134103055688292413446595077216756761727193028024470215530244768050010826877173235699637691655955908473771170645074319262049197935623437229535078523321083906712227132222561047274756830518955068353360103889423586339385295309120615979992652763536784371821993620190106467929309258100886903422590542463117376022420894267089887465438053796937919013636309925359
public exponent: 65537
问题是由 MGF1 摘要的不同值引起的:SunJCE 提供程序仅将 SHA-384 用于
RSA/ECB/OAEPWithSHA-384AndMGF1Padding
的 OAEP 摘要,而默认 SHA1 摘要应用于 MGF1 摘要。RSAOAEP384
,SHA-384 用于 both 摘要、OAEP 和 MGF1 摘要。这导致了两个代码的不兼容。
作为修复,必须使用
OAEPParameterSpec
: 将两个摘要显式设置为 SHA-384
...
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPADDING");
OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParameterSpec);
...
或者,可以使用 BouncyCastle 提供程序(例如,如果 BouncyCastle 已被应用)。这会将 OAEP 和 MGF1 摘要设置为指定值:
...
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("RSA/NONE/OAEPWithSHA384AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
...
encryptPubKeyWithPassword()
的完整代码:
public static String encryptPubKeyWithPassword(String pubKey, String password, String version) throws Exception {
String encodedPassword = URLEncoder.encode(password, StandardCharsets.UTF_8);
RSAPublicKey publicKey = createRsaPublicKey(pubKey);
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPADDING");
OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-384", "MGF1", MGF1ParameterSpec.SHA384, PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParameterSpec);
StringBuilder encryptedPasswordBuilder = new StringBuilder();
for (int i = 0; i < encodedPassword.length(); i += 270) {
String currentValue = encodedPassword.substring(i, Math.min(i + 270, encodedPassword.length()));
if (encryptedPasswordBuilder.length() > 0) encryptedPasswordBuilder.append("00000001");
encryptedPasswordBuilder.append(Base64.getEncoder().encodeToString(cipher.doFinal(currentValue.getBytes(StandardCharsets.UTF_8))));
}
return encryptedPasswordBuilder.toString();
}
另一个困惑是
jsrsasign库的
BigInteger
值是否包含正确的模数(如下所示的情况)。X509EncodedKeySpec
实例中使用 RSAPublicKey
导入密钥后确定模数。已发布的公共 PEM 密钥的模数为(十进制):
3953226311687758595392111994184029919105572353883850175466519164541873393823923732214781564401042342584241029257336199908139796366497541069327180034755296728788397456817216176094110417813365270437034224139108683316529614756336144052543998303152918929941819677301941145250628636498495113956820927519270970709149908767271627169825801967465740997006710347979917608611090978116377701630380908449017037401297495565187968809576663765854765455615940054260715071874403034550696249783288415681844605115585869191283022766347390049238750984142454221808268833091744962848703466396221134103055688292413446595077216756761727193028024470215530244768050010826877173235699637691655955908473771170645074319262049197935623437229535078523321083906712227132222561047274756830518955068353360103889423586339385295309120615979992652763536784371821993620190106467929309258100886903422590542463117376022420894267089887465438053796937919013636309925359
jsrsasign 使用 jsbn 库来实现
BigInteger
。这会将 BigInteger
internally 的值存储在数组中。实际值不仅仅是数组中各个值的串联。相反,可以按如下方式重建原始值:数组的值将以大端顺序进行十六进制编码,并且十六进制值将以相反的顺序连接(109, 108,..., 1, 0):
'109': 713517 --> 0xae32d
'108': 258381146, --> 0xf66955a
...
'1': 137037630, --> 0x82b073e
'0': 58867183, --> 0x3823def
这给出:
ae32df66955a930036113c068d02d503ee5ee50603352bb65767d63c2bdebb1305ef611c281fccc3e731e977bf005c1dcacbb70c179f1fc16846c2f81e60aa8a5add6d3ac6aa22ef67450b77bbe1f18edb053dfdea17eba3b0a44be02d6ae17d33118b5dd44dd402e93680f630360acbfe164fb04acb4100b7607176d37ce3582d0e9613b02d10bfdfaf11c23d3c6d8d23b9d664e4403ac92d640d71a6c3014e5b838b3b1572302bd21281943a4f86cf9459eba06a2abb6d09b919b952bda607b2244024eb1ee5b6dae6045eb298301561818affc4743490f162c1d1149f8fbcd639097a5e5c4f5e4cca1ad6d75bb90fe354390ad2669515da11a033291ca0532c4d61b76d026fcfd1a637c12d5bfeca0da930678ab16e519968f15543d82cbfc4fb3f8adf8656786dcedaedd55b206342bf5c8d989ad27241cd5526c8081ed264504c70a79b3d6e7f7a803fd4c6fbc34b9916e086ca7d332c59da01703ce40985e036f5cde037eeccab2eed9e10e95d22a973925278a804dd82b073e3823def
如果该值被解释为无符号大端十六进制值,则会产生正确的十进制模数。
但是,实际上不需要了解内部数据处理,因为导入的数据会自动转换为内部格式,并且
BigInteger
函数(toString()
、toRadix()
、toByteArray()
,...)返回数据格式正确,如以下 JavaScript 所示:
var bi = new jsbn.BigInteger('3953226311687758595392111994184029919105572353883850175466519164541873393823923732214781564401042342584241029257336199908139796366497541069327180034755296728788397456817216176094110417813365270437034224139108683316529614756336144052543998303152918929941819677301941145250628636498495113956820927519270970709149908767271627169825801967465740997006710347979917608611090978116377701630380908449017037401297495565187968809576663765854765455615940054260715071874403034550696249783288415681844605115585869191283022766347390049238750984142454221808268833091744962848703466396221134103055688292413446595077216756761727193028024470215530244768050010826877173235699637691655955908473771170645074319262049197935623437229535078523321083906712227132222561047274756830518955068353360103889423586339385295309120615979992652763536784371821993620190106467929309258100886903422590542463117376022420894267089887465438053796937919013636309925359');
console.log(bi.toRadix(16)) // hex
console.log(bi.toRadix(10)) // dec
console.log(bi.toString()) // dec
console.log(bi.toByteArray()) // signed array
console.log(bi) // internal data representation
<script src="https://cdn.jsdelivr.net/npm/[email protected]/index.min.js"></script>