package org.pzone.crypto; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.SecureRandom; import java.util.Arrays; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECPoint; /** * SM2公钥加密算法实现 包括 -签名,验签 -密钥交换 -公钥加密,私钥解密 * * @author Potato * */ public class SM2 { private static BigInteger n = new BigInteger( "FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "7203DF6B" + "21C6052B" + "53BBF409" + "39D54123", 16); private static BigInteger p = new BigInteger( "FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFF", 16); private static BigInteger a = new BigInteger( "FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFC", 16); private static BigInteger b = new BigInteger( "28E9FA9E" + "9D9F5E34" + "4D5A9E4B" + "CF6509A7" + "F39789F5" + "15AB8F92" + "DDBCBD41" + "4D940E93", 16); private static BigInteger gx = new BigInteger( "32C4AE2C" + "1F198119" + "5F990446" + "6A39C994" + "8FE30BBF" + "F2660BE1" + "715A4589" + "334C74C7", 16); private static BigInteger gy = new BigInteger( "BC3736A2" + "F4F6779C" + "59BDCEE3" + "6B692153" + "D0A9877C" + "C62A4740" + "02DF32E5" + "2139F0A0", 16); private static ECDomainParameters ecc_bc_spec; private static int w = (int) Math.ceil(n.bitLength() * 1.0 / 2) - 1; private static BigInteger _2w = new BigInteger("2").pow(w); private static final int DIGEST_LENGTH = 32; private static SecureRandom random = new SecureRandom(); private static ECCurve.Fp curve; private static ECPoint G; private boolean debug = false; public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } /** * �?16进制打印字节数组 * * @param b */ public static void printHexString(byte[] b) { for (int i = 0; i < b.length; i++) { String hex = Integer.toHexString(b[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } System.out.print(hex.toUpperCase()); } System.out.println(); } /** * 随机数生成器 * * @param max * @return */ private static BigInteger random(BigInteger max) { BigInteger r = new BigInteger(256, random); // int count = 1; while (r.compareTo(max) >= 0) { r = new BigInteger(128, random); // count++; } // System.out.println("count: " + count); return r; } /** * 判断字节数组是否�?0 * * @param buffer * @return */ private boolean allZero(byte[] buffer) { for (int i = 0; i < buffer.length; i++) { if (buffer[i] != 0) return false; } return true; } /** * 公钥加密 * * @param input * 加密原文 * @param publicKey * 公钥 * @return */ public byte[] encrypt(String input, ECPoint publicKey) { byte[] inputBuffer = input.getBytes(); if (debug) printHexString(inputBuffer); byte[] C1Buffer; ECPoint kpb; byte[] t; do { /* 1 产生随机数k,k属于[1, n-1] */ BigInteger k = random(n); if (debug) { System.out.print("k: "); printHexString(k.toByteArray()); } /* 2 计算椭圆曲线点C1 = [k]G = (x1, y1) */ ECPoint C1 = G.multiply(k); C1Buffer = C1.getEncoded(false); if (debug) { System.out.print("C1: "); printHexString(C1Buffer); } /* * 3 计算椭圆曲线�? S = [h]Pb */ BigInteger h = ecc_bc_spec.getH(); if (h != null) { ECPoint S = publicKey.multiply(h); if (S.isInfinity()) throw new IllegalStateException(); } /* 4 计算 [k]PB = (x2, y2) */ kpb = publicKey.multiply(k).normalize(); /* 5 计算 t = KDF(x2||y2, klen) */ byte[] kpbBytes = kpb.getEncoded(false); t = KDF(kpbBytes, inputBuffer.length); // DerivationFunction kdf = new KDF1BytesGenerator(new // ShortenedDigest(new SHA256Digest(), DIGEST_LENGTH)); // // t = new byte[inputBuffer.length]; // kdf.init(new ISO18033KDFParameters(kpbBytes)); // kdf.generateBytes(t, 0, t.length); } while (allZero(t)); /* 6 计算C2=M^t */ byte[] C2 = new byte[inputBuffer.length]; for (int i = 0; i < inputBuffer.length; i++) { C2[i] = (byte) (inputBuffer[i] ^ t[i]); } /* 7 计算C3 = Hash(x2 || M || y2) */ byte[] C3 = sm3hash(kpb.getXCoord().toBigInteger().toByteArray(), inputBuffer, kpb.getYCoord().toBigInteger().toByteArray()); /* 8 输出密文 C=C1 || C2 || C3 */ byte[] encryptResult = new byte[C1Buffer.length + C2.length + C3.length]; System.arraycopy(C1Buffer, 0, encryptResult, 0, C1Buffer.length); System.arraycopy(C2, 0, encryptResult, C1Buffer.length, C2.length); System.arraycopy(C3, 0, encryptResult, C1Buffer.length + C2.length, C3.length); if (debug) { System.out.print("密文: "); printHexString(encryptResult); } return encryptResult; } /** * 私钥解密 * * @param encryptData * 密文数据字节数组 * @param privateKey * 解密私钥 * @return */ public String decrypt(byte[] encryptData, BigInteger privateKey) { if (debug) System.out.println("encryptData length: " + encryptData.length); byte[] C1Byte = new byte[65]; System.arraycopy(encryptData, 0, C1Byte, 0, C1Byte.length); ECPoint C1 = curve.decodePoint(C1Byte).normalize(); /* * 计算椭圆曲线�? S = [h]C1 是否为无穷点 */ BigInteger h = ecc_bc_spec.getH(); if (h != null) { ECPoint S = C1.multiply(h); if (S.isInfinity()) throw new IllegalStateException(); } /* 计算[dB]C1 = (x2, y2) */ ECPoint dBC1 = C1.multiply(privateKey).normalize(); /* 计算t = KDF(x2 || y2, klen) */ byte[] dBC1Bytes = dBC1.getEncoded(false); int klen = encryptData.length - 65 - DIGEST_LENGTH; byte[] t = KDF(dBC1Bytes, klen); // DerivationFunction kdf = new KDF1BytesGenerator(new // ShortenedDigest(new SHA256Digest(), DIGEST_LENGTH)); // if (debug) // System.out.println("klen = " + klen); // kdf.init(new ISO18033KDFParameters(dBC1Bytes)); // kdf.generateBytes(t, 0, t.length); if (allZero(t)) { System.err.println("all zero"); throw new IllegalStateException(); } /* 5 计算M'=C2^t */ byte[] M = new byte[klen]; for (int i = 0; i < M.length; i++) { M[i] = (byte) (encryptData[C1Byte.length + i] ^ t[i]); } if (debug) printHexString(M); /* 6 计算 u = Hash(x2 || M' || y2) 判断 u == C3是否成立 */ byte[] C3 = new byte[DIGEST_LENGTH]; if (debug) try { System.out.println("M = " + new String(M, "UTF8")); } catch (UnsupportedEncodingException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } System.arraycopy(encryptData, encryptData.length - DIGEST_LENGTH, C3, 0, DIGEST_LENGTH); byte[] u = sm3hash(dBC1.getXCoord().toBigInteger().toByteArray(), M, dBC1.getYCoord().toBigInteger().toByteArray()); if (Arrays.equals(u, C3)) { if (debug) System.out.println("解密成功"); try { return new String(M, "UTF8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } else { if (debug) { System.out.print("u = "); printHexString(u); System.out.print("C3 = "); printHexString(C3); System.err.println("解密验证失败"); } return null; } } // /** // * SHA摘要 // * @param x2 // * @param M // * @param y2 // * @return // */ // private byte[] calculateHash(BigInteger x2, byte[] M, BigInteger y2) { // ShortenedDigest digest = new ShortenedDigest(new SHA256Digest(), // DIGEST_LENGTH); // byte[] buf = x2.toByteArray(); // digest.update(buf, 0, buf.length); // digest.update(M, 0, M.length); // buf = y2.toByteArray(); // digest.update(buf, 0, buf.length); // // buf = new byte[DIGEST_LENGTH]; // digest.doFinal(buf, 0); // // return buf; // } /** * 判断是否在范围内 * * @param param * @param min * @param max * @return */ private boolean between(BigInteger param, BigInteger min, BigInteger max) { if (param.compareTo(min) >= 0 && param.compareTo(max) < 0) { return true; } else { return false; } } /** * 判断生成的公钥是否合�? * * @param publicKey * @return */ private boolean checkPublicKey(ECPoint publicKey) { if (!publicKey.isInfinity()) { BigInteger x = publicKey.getXCoord().toBigInteger(); BigInteger y = publicKey.getYCoord().toBigInteger(); if (between(x, new BigInteger("0"), p) && between(y, new BigInteger("0"), p)) { BigInteger xResult = x.pow(3).add(a.multiply(x)).add(b).mod(p); if (debug) System.out.println("xResult: " + xResult.toString()); BigInteger yResult = y.pow(2).mod(p); if (debug) System.out.println("yResult: " + yResult.toString()); if (yResult.equals(xResult) && publicKey.multiply(n).isInfinity()) { return true; } } } return false; } /** * 生成密钥�? * * @return */ public SM2KeyPair generateKeyPair() { BigInteger d = random(n.subtract(new BigInteger("1"))); SM2KeyPair keyPair = new SM2KeyPair(G.multiply(d).normalize(), d); if (checkPublicKey(keyPair.getPublicKey())) { if (debug) System.out.println("generate key successfully"); return keyPair; } else { if (debug) System.err.println("generate key failed"); return null; } } public SM2() { curve = new ECCurve.Fp(p, // q a, // a b); // b G = curve.createPoint(gx, gy); ecc_bc_spec = new ECDomainParameters(curve, G, n); } public SM2(boolean debug) { this(); this.debug = debug; } /** * 导出公钥到本�? * * @param publicKey * @param path */ public void exportPublicKey(ECPoint publicKey, String path) { File file = new File(path); try { if (!file.exists()) file.createNewFile(); byte buffer[] = publicKey.getEncoded(false); FileOutputStream fos = new FileOutputStream(file); fos.write(buffer); fos.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 从本地导入公�? * * @param path * @return */ public ECPoint importPublicKey(String path) { File file = new File(path); try { if (!file.exists()) return null; FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte buffer[] = new byte[16]; int size; while ((size = fis.read(buffer)) != -1) { baos.write(buffer, 0, size); } fis.close(); return curve.decodePoint(baos.toByteArray()); } catch (IOException e) { e.printStackTrace(); } return null; } /** * 导出私钥到本�? * * @param privateKey * @param path */ public void exportPrivateKey(BigInteger privateKey, String path) { File file = new File(path); try { if (!file.exists()) file.createNewFile(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(privateKey); oos.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 从本地导入私�? * * @param path * @return */ public BigInteger importPrivateKey(String path) { File file = new File(path); try { if (!file.exists()) return null; FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); BigInteger res = (BigInteger) (ois.readObject()); ois.close(); fis.close(); return res; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 字节数组拼接 * * @param params * @return */ private static byte[] join(byte[]... params) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] res = null; try { for (int i = 0; i < params.length; i++) { baos.write(params[i]); } res = baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return res; } /** * sm3摘要 * * @param params * @return */ private static byte[] sm3hash(byte[]... params) { byte[] res = null; try { res = SM3.hash(join(params)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return res; } /** * 取得用户标识字节数组 * * @param IDA * @param aPublicKey * @return */ private static byte[] ZA(String IDA, ECPoint aPublicKey) { byte[] idaBytes = IDA.getBytes(); int entlenA = idaBytes.length * 8; byte[] ENTLA = new byte[] { (byte) (entlenA & 0xFF00), (byte) (entlenA & 0x00FF) }; byte[] ZA = sm3hash(ENTLA, idaBytes, a.toByteArray(), b.toByteArray(), gx.toByteArray(), gy.toByteArray(), aPublicKey.getXCoord().toBigInteger().toByteArray(), aPublicKey.getYCoord().toBigInteger().toByteArray()); return ZA; } /** * 签名 * * @param M * 签名信息 * @param IDA * 签名方唯�?标识 * @param keyPair * 签名方密钥对 * @return 签名 */ public Signature sign(String M, String IDA, SM2KeyPair keyPair) { byte[] ZA = ZA(IDA, keyPair.getPublicKey()); byte[] M_ = join(ZA, M.getBytes()); BigInteger e = new BigInteger(1, sm3hash(M_)); // BigInteger k = new BigInteger( // "6CB28D99 385C175C 94F94E93 4817663F C176D925 DD72B727 260DBAAE // 1FB2F96F".replace(" ", ""), 16); BigInteger k; BigInteger r; do { k = random(n); ECPoint p1 = G.multiply(k).normalize(); BigInteger x1 = p1.getXCoord().toBigInteger(); r = e.add(x1); r = r.mod(n); } while (r.equals(BigInteger.ZERO) || r.add(k).equals(n)); BigInteger s = ((keyPair.getPrivateKey().add(BigInteger.ONE).modInverse(n)) .multiply((k.subtract(r.multiply(keyPair.getPrivateKey()))).mod(n))).mod(n); return new Signature(r, s); } /** * 验签 * * @param M * 签名信息 * @param signature * 签名 * @param IDA * 签名方唯�?标识 * @param aPublicKey * 签名方公�? * @return true or false */ public boolean verify(String M, Signature signature, String IDA, ECPoint aPublicKey) { if (!between(signature.r, BigInteger.ONE, n)) return false; if (!between(signature.s, BigInteger.ONE, n)) return false; byte[] M_ = join(ZA(IDA, aPublicKey), M.getBytes()); BigInteger e = new BigInteger(1, sm3hash(M_)); BigInteger t = signature.r.add(signature.s).mod(n); if (t.equals(BigInteger.ZERO)) return false; ECPoint p1 = G.multiply(signature.s).normalize(); ECPoint p2 = aPublicKey.multiply(t).normalize(); BigInteger x1 = p1.add(p2).normalize().getXCoord().toBigInteger(); BigInteger R = e.add(x1).mod(n); if (R.equals(signature.r)) return true; return false; } /** * 密钥派生函数 * * @param Z * @param klen * 生成klen字节数长度的密钥 * @return */ private static byte[] KDF(byte[] Z, int klen) { int ct = 1; int end = (int) Math.ceil(klen * 1.0 / 32); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { for (int i = 1; i < end; i++) { baos.write(sm3hash(Z, SM3.toByteArray(ct))); ct++; } byte[] last = sm3hash(Z, SM3.toByteArray(ct)); if (klen % 32 == 0) { baos.write(last); } else baos.write(last, 0, klen % 32); return baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 传输实体�? * * @author Potato * */ private static class TransportEntity implements Serializable { final byte[] R; //R�? final byte[] S; //验证S final byte[] Z; //用户标识 final byte[] K; //公钥 public TransportEntity(byte[] r, byte[] s,byte[] z,ECPoint pKey) { R = r; S = s; Z=z; K=pKey.getEncoded(false); } } /** * 密钥协商辅助�? * * @author Potato * */ public static class KeyExchange { BigInteger rA; ECPoint RA; ECPoint V; byte[] Z; byte[] key; String ID; SM2KeyPair keyPair; public KeyExchange(String ID,SM2KeyPair keyPair) { this.ID=ID; this.keyPair = keyPair; this.Z=ZA(ID, keyPair.getPublicKey()); } /** * 密钥协商发起第一�? * * @return */ public TransportEntity keyExchange_1() { rA = random(n); // rA=new BigInteger("83A2C9C8 B96E5AF7 0BD480B4 72409A9A 327257F1 // EBB73F5B 073354B2 48668563".replace(" ", ""),16); RA = G.multiply(rA).normalize(); return new TransportEntity(RA.getEncoded(false), null,Z,keyPair.getPublicKey()); } /** * 密钥协商响应�? * * @param entity 传输实体 * @return */ public TransportEntity keyExchange_2(TransportEntity entity) { BigInteger rB = random(n); // BigInteger rB=new BigInteger("33FE2194 0342161C 55619C4A 0C060293 // D543C80A F19748CE 176D8347 7DE71C80".replace(" ", ""),16); ECPoint RB = G.multiply(rB).normalize(); this.rA=rB; this.RA=RB; BigInteger x2 = RB.getXCoord().toBigInteger(); x2 = _2w.add(x2.and(_2w.subtract(BigInteger.ONE))); BigInteger tB = keyPair.getPrivateKey().add(x2.multiply(rB)).mod(n); ECPoint RA = curve.decodePoint(entity.R).normalize(); BigInteger x1 = RA.getXCoord().toBigInteger(); x1 = _2w.add(x1.and(_2w.subtract(BigInteger.ONE))); ECPoint aPublicKey=curve.decodePoint(entity.K).normalize(); ECPoint temp = aPublicKey.add(RA.multiply(x1).normalize()).normalize(); ECPoint V = temp.multiply(ecc_bc_spec.getH().multiply(tB)).normalize(); if (V.isInfinity()) throw new IllegalStateException(); this.V=V; byte[] xV = V.getXCoord().toBigInteger().toByteArray(); byte[] yV = V.getYCoord().toBigInteger().toByteArray(); byte[] KB = KDF(join(xV, yV, entity.Z, this.Z), 16); key = KB; System.out.print("协商得B密钥:"); printHexString(KB); byte[] sB = sm3hash(new byte[] { 0x02 }, yV, sm3hash(xV, entity.Z, this.Z, RA.getXCoord().toBigInteger().toByteArray(), RA.getYCoord().toBigInteger().toByteArray(), RB.getXCoord().toBigInteger().toByteArray(), RB.getYCoord().toBigInteger().toByteArray())); return new TransportEntity(RB.getEncoded(false), sB,this.Z,keyPair.getPublicKey()); } /** * 密钥协商发起方第二� * * @param entity 传输实体 */ public TransportEntity keyExchange_3(TransportEntity entity) { BigInteger x1 = RA.getXCoord().toBigInteger(); x1 = _2w.add(x1.and(_2w.subtract(BigInteger.ONE))); BigInteger tA = keyPair.getPrivateKey().add(x1.multiply(rA)).mod(n); ECPoint RB = curve.decodePoint(entity.R).normalize(); BigInteger x2 = RB.getXCoord().toBigInteger(); x2 = _2w.add(x2.and(_2w.subtract(BigInteger.ONE))); ECPoint bPublicKey=curve.decodePoint(entity.K).normalize(); ECPoint temp = bPublicKey.add(RB.multiply(x2).normalize()).normalize(); ECPoint U = temp.multiply(ecc_bc_spec.getH().multiply(tA)).normalize(); if (U.isInfinity()) throw new IllegalStateException(); this.V=U; byte[] xU = U.getXCoord().toBigInteger().toByteArray(); byte[] yU = U.getYCoord().toBigInteger().toByteArray(); byte[] KA = KDF(join(xU, yU, this.Z, entity.Z), 16); key = KA; System.out.print("协商得A密钥:"); printHexString(KA); byte[] s1= sm3hash(new byte[] { 0x02 }, yU, sm3hash(xU, this.Z, entity.Z, RA.getXCoord().toBigInteger().toByteArray(), RA.getYCoord().toBigInteger().toByteArray(), RB.getXCoord().toBigInteger().toByteArray(), RB.getYCoord().toBigInteger().toByteArray())); if(Arrays.equals(entity.S, s1)) System.out.println("B->A 密钥确认成功"); else System.out.println("B->A 密钥确认失败"); byte[] sA= sm3hash(new byte[] { 0x03 }, yU, sm3hash(xU, this.Z, entity.Z, RA.getXCoord().toBigInteger().toByteArray(), RA.getYCoord().toBigInteger().toByteArray(), RB.getXCoord().toBigInteger().toByteArray(), RB.getYCoord().toBigInteger().toByteArray())); return new TransportEntity(RA.getEncoded(false), sA,this.Z,keyPair.getPublicKey()); } /** * 密钥确认�?后一�? * * @param entity 传输实体 */ public void keyExchange_4(TransportEntity entity) { byte[] xV = V.getXCoord().toBigInteger().toByteArray(); byte[] yV = V.getYCoord().toBigInteger().toByteArray(); ECPoint RA = curve.decodePoint(entity.R).normalize(); byte[] s2= sm3hash(new byte[] { 0x03 }, yV, sm3hash(xV, entity.Z, this.Z, RA.getXCoord().toBigInteger().toByteArray(), RA.getYCoord().toBigInteger().toByteArray(), this.RA.getXCoord().toBigInteger().toByteArray(), this.RA.getYCoord().toBigInteger().toByteArray())); if(Arrays.equals(entity.S, s2)) System.out.println("A->B 密钥确认成功"); else System.out.println("A->B 密钥确认失败"); } } public static void main(String[] args) throws UnsupportedEncodingException { SM2 sm02 = new SM2(); /* BigInteger px = new BigInteger( "0AE4C779 8AA0F119 471BEE11 825BE462 02BB79E2 A5844495 E97C04FF 4DF2548A".replace(" ", ""), 16); BigInteger py = new BigInteger( "7C0240F8 8F1CD4E1 6352A73C 17B7F16F 07353E53 A176D684 A9FE0C6B B798E857".replace(" ", ""), 16); ECPoint publicKey = sm02.curve.createPoint(px, py); BigInteger privateKey = new BigInteger( "128B2FA8 BD433C6C 068C8D80 3DFF7979 2A519A55 171B1B65 0C23661D 15897263".replace(" ", ""), 16); */ SM2KeyPair keyPair = sm02.generateKeyPair(); ECPoint publicKey=keyPair.getPublicKey(); BigInteger privateKey=keyPair.getPrivateKey(); sm02.exportPublicKey(publicKey, "E:/publickey.pem"); sm02.exportPrivateKey(privateKey, "E:/privatekey.pem"); System.out.println("-----------------公钥加密与解�?-----------------"); /*ECPoint*/ publicKey = sm02.importPublicKey("E:/publickey.pem"); /*BigInteger*/ privateKey = sm02.importPrivateKey("E:/privatekey.pem"); byte[] data = sm02.encrypt("测试加密aaaaaaaaaaa123aabb", publicKey); System.out.print("密文:"); SM2.printHexString(data); System.out.println("解密后明�?:" + sm02.decrypt(data, privateKey)); System.out.println("-----------------签名与验�?-----------------"); String IDA = "Heartbeats"; String M = "要签名的信息"; Signature signature = sm02.sign(M, IDA, new SM2KeyPair(publicKey, privateKey)); System.out.println("用户标识:" + IDA); System.out.println("签名信息:" + M); System.out.println("数字签名:" + signature); System.out.println("验证签名:" + sm02.verify(M, signature, IDA, publicKey)); System.out.println("-----------------密钥协商-----------------"); String aID = "AAAAAAAAAAAAA"; SM2KeyPair aKeyPair = sm02.generateKeyPair(); KeyExchange aKeyExchange = new KeyExchange(aID,aKeyPair); String bID = "BBBBBBBBBBBBB"; SM2KeyPair bKeyPair = sm02.generateKeyPair(); KeyExchange bKeyExchange = new KeyExchange(bID,bKeyPair); TransportEntity entity1 = aKeyExchange.keyExchange_1(); TransportEntity entity2 = bKeyExchange.keyExchange_2(entity1); TransportEntity entity3 = aKeyExchange.keyExchange_3(entity2); bKeyExchange.keyExchange_4(entity3); } public static class Signature { BigInteger r; BigInteger s; public Signature(BigInteger r, BigInteger s) { this.r = r; this.s = s; } public String toString() { return r.toString(16) + "," + s.toString(16); } } }