Java RSAPublicKey interface

I need to get get the Modulus and exponent of a RSA public key
This requires the java Interface RSAPublicKey see RSAPublicKey (Java Platform SE 8 )

The java code:
RSAPublicKey rsaPub = (RSAPublicKey)(kp.getPublic());
BigInteger modulus = rsaPub.getModulus();
BigInteger publicExponent = rsaPub.getPublicExponent();

(RSAPublicKey) is a java interface and I cannot figure out how to use it with lucee.
Any suggestions?

try doing a search on github Sign in to GitHub Ā· GitHub

Try this:

publicKey = [yourBase64String];
keyBytes = BinaryDecode( publicKey, "base64" );
keyFactory = CreateObject( "java", "java.security.KeyFactory" ).getInstance( "RSA" );
spec = CreateObject( "java", "java.security.spec.X509EncodedKeySpec" ).init( keyBytes );
rsaPublicKey = keyFactory.generatePublic( spec );
publicKeyModulus = rsaPublicKey.getModulus();
publicKeyExponent = rsaPublicKey.getPublicExponent();
1 Like

Thanks Julian, i don’t think my key is ā€œX509EncodedKeySpecā€ but is ā€œRSAPublicKeyā€ so that example gives me a java.security.spec.InvalidKeySpecException.

Here is an example public key created using openpgp.js:

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v4.10.10
Comment: https://openpgpjs.org

xsBNBGEPAvYBCACzxODWwc2Rautri/3h9oBVbinOQvOVPqI/1MvvKFWbeX5h
52rRbjuYtcjsWsLlu4dpGXr+QczOUta8wjEh2ri2QeXFmWVI8UtI+qE2fNtE
M4ohy5EJ7AFC0PWB9o6UJ1XPwXgs6KmFnmk6HeLXPqPiQoSM0584z0Vdfd9d
3lRwicWmi2aWKAQHAhrJ+uYij+MH9cpQiWaXBMYqFPRwMrUiCH9HC5Mgb3W2
YQHAwoWus+q2h2TeRvaWUnFNfw32OkrBo2yukXkzAc32riH6O/+waHCy8/oP
d1CK8ZEN5pFxmNnbdvDCOEIeJ5d4dnotKoaXiPBz2DLqFWBiappjSbTHABEB
AAHNFEdvZCA8R29kQGhlYXZlbi5jb20+wsCNBBABCAAgBQJhDwL2BgsJBwgD
AgQVCAoCBBYCAQACGQECGwMCHgEAIQkQcpXBofxy26YWIQRC/7qpiT7G4YpD
0rpylcGh/HLbpq1nB/9uS0F5DxkayLi2AoqPasRpGpcx9NykJm40qqUIl3BN
MKhdVvDeYglYRIPbcm9QOqEI7OdWdTo3Z/bfEbkorZNT6S6wj6s+iuzLlbC/
dNPcyh/HNT/XET/YN4S3EPBk8SxygiZYWtk6nas6wMq4hFPqxLJ+/BYzILaL
qrqicipECxY+sNHJCI3CyMbGJ76+o8CIdNdVF9Oli9crsBMxRYoyXQfV9woZ
s+2i+iwcgw7CKBEPvk3GDCpAga5vPbUELp49kTC9BHjRcKzhoea1w6ak9RIC
lvHtK9U2uSICIbB5p1OZf3T6Rqlnk9pXJXavyMg8Wd4Cvn06rmppRh0fmBYN
zsBNBGEPAvYBCADMQiHM4U1vwI4WutmQ8fZzeDKG7h+bgw07swTTEXwX76DC
RhxktThdDSDzRyaHxVnRvRhR5530zBDUAYQDfI/x/f6bVqHVotBVNAlUrYGh
cVcgow5t5ZzLTmGyxPq9fkvZFffZcKHQpIOjrD+c7snrdJZTeoQ5qHMwj4nj
JfQ+DquwfDhWy659IPnvn14lioy/ky2deNHsxTp3+JdN1HyVP+/WGnABn+Mq
GNF9nhXe7ySpvxe/xVg3QaoB/Iq6McoaYAs+Nz8CPMycOAQSRDwVXhgPyOlw
BO+DqCvj43SsIeeKW8bSHjCsBcIZ5UDJjy0dSt5Agj71ltuAEdoTQU0LABEB
AAHCwHYEGAEIAAkFAmEPAvYCGwwAIQkQcpXBofxy26YWIQRC/7qpiT7G4YpD
0rpylcGh/HLbpj3OB/9EWAfYLBCc1s9YgnJBnfJnWCLJXQmBadXLIDr7RdVO
FRVr7hXzkIDrRT1DBzP/WdXzNwhnBIpQaoKj3CG7oSrtgNjSQeshS2FnhC7W
rvh/w2J54SO+wZuaDGL+oHI51wYvHOiV5R+Ymj/TrSGcM0sEn/3yc4+wZu9M
tbEsBAi3wp4KjSGSljb1Azd/w+hoLARHMic1Hn5ky7rbb+6sXVlEEmi1UB6k
6y7Ww1/9x8hux++VTwVxXdNzqkomTznTwBdDLUSmUyISm21YyQpBc+Grgl6B
w8A+SX3neapRD53gEhbOz91edm+4UY3clA0XuVBEROMvg48FnIDarg4Rvkrl
-----END PGP PUBLIC KEY BLOCK-----

I’m currently trying to create a block of java code → jar to drop in my bundles folder to parse the RSAPublicKey interface, as I can’t find a direct way using lucee.

My ultimate goal is to sign something in openpgp,js and validate the signature on the server.

Sorry my suggestion didn’t work for you Andrew. It seems OK when using Lucee’s (fairly) new GenerateRSAKeys() function:

publicKey = GenerateRSAKeys().public;
keyBytes = BinaryDecode( publicKey, "base64" );
keyFactory = CreateObject( "java", "java.security.KeyFactory" ).getInstance( "RSA" );
spec = CreateObject( "java", "java.security.spec.X509EncodedKeySpec" ).init( keyBytes );
rsaPublicKey = keyFactory.generatePublic( spec );
result.publicKeyModulus = rsaPublicKey.getModulus();
result.publicKeyExponent = rsaPublicKey.getPublicExponent();
dump( result );

But as you can tell I’m way out of my depth in terms of understanding crypto.

All I can add is maybe some pointers:

  • The problem with the RSAPublicKeySpec is that it requires the modulus and exponent values in the constructor, so that can’t just be substituted for the X509 spec.
  • You’ve generated your key for PGP so maybe you could look at the Bouncy Castle openPGP API. I gave it a quick go, but ran into issues.
  • Returning to your original question, I was able to translate your java code with the RSAPublicKey interface to CFML as follows:
kpg = CreateObject( "java", "java.security.KeyPairGenerator" ).getInstance( "RSA" );
kpg.initialize( 2048 );
kp = kpg.genKeyPair();
publicKey = kp.getPublic();
rsaPublicKey = JavaCast( "java.security.interfaces.RSAPublicKey", publicKey );
result.publicKeyModulus = rsaPublicKey.getModulus();
result.publicKeyExponent = rsaPublicKey.getPublicExponent();
dump( result );

That’s working with a new key pair though. I still don’t know how to read your existing key into an object I’m afraid.

Thanks Julian, I think your suggestion of using Bouncy Castle is the way to go. If I find a solution I’ll report back here.

After going down a million rabbit holes I got it working!
This test shows two methods: either reading the public key, signature and signed text from within the code or from file (ā€˜usefile’ - true/false).
Signed with a ECC curve, can also use RSA.
There is no error handling for malformed signatures/public keys or incorrect public key.
I wasted a lot of time because I was incorrectly copying and pasting the signed file ā€œtestā€, the original example had a line break chr(10) ā€˜\n’ at end which I missed, this example has a <br> at end.

<!--- for jar file (bcpg-jdk15on-169.jar) see: https://www.bouncycastle.org/latest_releases.html
 Lucee - put jar in bundles folder: bcpg-jdk15on-169.jar
 
 java version: 1.8.0_201 (Oracle Corporation) 64bit
  --->

<cfset ByteArrayInputStream = CreateObject("java", "java.io.ByteArrayInputStream")>
<cfset StandardCharsets = CreateObject("java", "java.nio.charset.StandardCharsets")>
<cfset BufferedInputStream = CreateObject("java", "java.io.BufferedInputStream")>
<cfset Security = CreateObject("java", "java.security.Security")>
<cfset Base64 = CreateObject("java", "java.util.Base64")>
<cfset FileInputStream = CreateObject("java", "java.io.FileInputStream")>
<cfset Long = CreateObject("java", "java.lang.Long")>

<cfset PGPObjectFactory = CreateObject("java", "org.bouncycastle.openpgp.PGPObjectFactory", "bcpg", "1.69.0")>

<cfset BcKeyFingerprintCalculator = CreateObject("java", "org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator", "bcpg", "1.69.0")>
<cfset PGPSignatureList = CreateObject("java", "org.bouncycastle.openpgp.PGPSignatureList", "bcpg", "1.69.0")>

<cfset PGPPublicKeyRingCollection = CreateObject("java", "org.bouncycastle.openpgp.PGPPublicKeyRingCollection", "bcpg", "1.69.0")>
<cfset PGPUtil = CreateObject("java", "org.bouncycastle.openpgp.PGPUtil", "bcpg", "1.69.0")>

<cfset BcPGPContentVerifierBuilderProvider = CreateObject("java", "org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider", "bcpg", "1.69.0")>

<cfset JcaPGPContentVerifierBuilderProvider = CreateObject("java", "org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider", "bcpg", "1.69.0")>

<cfset PGPContentVerifierBuilderProvider = CreateObject("java", "org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider", "bcpg", "1.69.0")>

<cfset JcaKeyFingerprintCalculator = CreateObject("java", "org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator", "bcpg", "1.69.0")>


<cfset pubkey = "-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v4.10.10
Comment: https://kodamail.com

xpMEYSQMghMJKyQDAwIIAQENBAMEMzh+ochOHrTCoShos6Igh1jn+vjZe35L
y701FgSa2UMzIf09ZDMsa5j26mvvZqVmmsnLpxpbyG8y2Kgx0k/ztYBJOQSa
uWI3pt2c8eIEkbNW/2irBDfgtkbMGzfH2J0Edpeo9usWUtydYKnPeiNcchka
lqd16sCRdBXh0juYM37NIUFuZHJldyBHcm9zc2V0IDxhZzU3NDNAZ21haWwu
Y29tPsLADwQQEwoAIAUCYSQMggYLCQcIAwIEFQgKAgQWAgEAAhkBAhsDAh4B
ACEJEB9Xkh/GktNtFiEELKKK+ioVLteSmHEYH1eSH8aS021+rAIAh1HYzroS
aEnHa96afXJPLEtzS/vz1DtNo2bLDqRIfTHFXZr7T9gKEAEWHIzo4W1Sjty5
szRscjinf+UTHHGyrAH/fRHJsWQWqnvkbx7OhLclYx89z8ZZYZ1ucnpsnmbF
Wj9bLYKmToI95TYM6V8IxVPVgxxdRyITx2A1nL2KxflqQs6XBGEkDIISCSsk
AwMCCAEBDQQDBGoRKGi7vtlO5dO42B5sliuygYdjlc3GEvUjsSj+7fwRWmj7
CRaJWSVkkMyoJVhShfOuxjG3BBAeBLOQNRdveTR5+XWApV48F0m/5qn9rn0Q
aBT7jdd3bX8Gakp4xv3FkkObUPgOelr6oKSQgJtTWoGnW4jsOTRJWlqPjB2Q
BTcZAwEKCcK4BBgTCgAJBQJhJAyCAhsMACEJEB9Xkh/GktNtFiEELKKK+ioV
LteSmHEYH1eSH8aS020vIQIAlP6lhJg+Dn1gaypg9OwyFRf9kmB0LeYqaIwy
KFd02wUsI5vWGaqnpSXXMs1u5NqoK4dclaO4JMlsQeXhWiEpBAH/eB4J/62v
x4H9DsdMx+sJ0Ixrm/Sb/r2cmKJ33s/1/KjN4eiPf4KxpItItEIvjBaLPfji
Z1NgrXNzszMQweh/oA==
=DN/d
-----END PGP PUBLIC KEY BLOCK-----">


<cfset sig="-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.10
Comment: https://kodamail.com

wrUEARMKAAYFAmEkDL8AIQkQH1eSH8aS020WIQQsoor6KhUu15KYcRgfV5If
xpLTbZMQAf0S25Tat7sbvsgFqwbSbW2Qy4SYwDGF3obW/VxwaNnP1pcJov5C
JMrYf5ud/cv1c37xSJZLFkKx3rUYG1SrFTIxAf4gODASYlP9BlYGnW/Ab3e6
G6ba5ZUXf9Lj/HseLHPKYpLREhUorEMZLwgwHAl9LfO3m7pu4c45w2Um5trn
UUG2
=0BCl
-----END PGP SIGNATURE-----">

<!--- In this example 'signedFile' the newline (<br>) is important! it can also be chr(10) - \n
      if copying/pasting this can easily be missed --->

<cfset signedFile = "test<br>"> <!--- test#Chr(10)# --->

<cftry>
<cfscript>
function removeArmor(str){
  if(find('-----BEGIN PGP SIGNATURE-----',str) || find('-----BEGIN PGP PUBLIC KEY BLOCK-----',str)){
	  var afterComment = find('#chr(10)##chr(13)#',str);
		var header = str.mid( start=1, count=afterComment );
		str = str.replace(header,'');
		str = str.replace('#chr(10)#-----END PGP SIGNATURE-----','');
		str = str.replace('#chr(10)#-----END PGP PUBLIC KEY BLOCK-----','');
		return str
	}
	return str;
}

//useful sources:
//http://www.java2s.com/example/java-src/pkg/cc/arduino/contributions/gpgdetachedsignatureverifier-6df76.html
//https://github.com/bcgit/bc-java/blob/master/pg/src/main/java/org/bouncycastle/openpgp/examples/DetachedSignatureProcessor.java

/*
If using Base64.getDecoder().decode(data) have to remove the checksum (=KzaN) at the end of base64 signature (sig) and public key (pubkey)
Alternatively BinaryDecode( data, "Base64" ) can be used and checksums can be left intact.
*/



function getStream(data){
  
	var bytes = BinaryDecode( data, "Base64" );//or Base64.getDecoder().decode(data);
	var byteArrayInputStream = ByteArrayInputStream.init(bytes);
	var BufferedInputStream = BufferedInputStream.init(byteArrayInputStream);
  byteArrayInputStream.close();
	
	return BufferedInputStream;
	
}

function verify(signedFile, signature, publicKey, usefile){
  
	var signatureInputStream;	
	
	if(usefile){
	  signatureInputStream = BufferedInputStream.init(FileInputStream.init(signature));
	}else{
	  signatureInputStream = getStream( removeArmor(signature) );
	}
	
	
	var pgpObjectFactory = PGPObjectFactory.init( PGPUtil.getDecoderStream(signatureInputStream), BcKeyFingerprintCalculator.init() );//
  	
	var nextObject = pgpObjectFactory.nextObject();
		
	var pgpSignatureList = nextObject;
  	
	signatureInputStream.close();//close BufferedInputStream
	
	var pgpSignature = pgpSignatureList.get(0);
	//dump(label='pgpSignature.getHashAlgorithm()',var=pgpSignature.getHashAlgorithm())//->10 (sha512)
	
	
	//getKeyID() feed this signature keyID (long) back to pgpPubRingCollection.getPublicKey(keyID)
	var keyID = pgpSignature.getKeyID();//2258434403322745600
	dump(label='keyID from signature and then toHexString(keyID)',var='#keyID# - #UCase(Long.toHexString(keyID))#');
			
	var pubStream;
	
	if(usefile){
	  pubStream = BufferedInputStream.init(FileInputStream.init(publicKey));
	}else{
	  pubStream = getStream( removeArmor(publicKey) );
	}	
	
	var pgpPubRingCollection = PGPPublicKeyRingCollection.init(PGPUtil.getDecoderStream(pubStream), JcaKeyFingerprintCalculator.init());
	
	pubStream.close();//close BufferedInputStream
	
	/* -----------------------------------------------------------------------------------------------------------------------------------
	var keyRingIter = pgpPubRingCollection.getKeyRings();
  
	var keyRing;
	var keyIter;
	var key;
	
	while (keyRingIter.hasNext()) {
	  
		keyRing = keyRingIter.next();
	  keyIter = keyRing.getPublicKeys();
		while (keyIter.hasNext()) {
		  
			key = keyIter.next();
			if (key.isMasterKey()){
			  dump(label='key.isMasterKey()',var=key.getKeyID());
			}
			dump(label='key',var=key);
			//dump(pgpPubRingCollection.getPublicKey(key.getKeyID()));
			dump(label='key.getKeyID()',var=key.getKeyID());
			dump(key.getCreationTime());
			userids = pgpPubRingCollection.getPublicKey(key.getKeyID()).getUserIDs()
			while(userids.hasNext()){
			  dump(userids.next());
				
			}
			
		}
		
	}
	-------------------------------------------------------------------------------------------------------------------------
	*/
	
	//get the public key according to keyID found in signature: 2258434403322745600
	var pgpPublicKey = pgpPubRingCollection.getPublicKey(keyID);
				
	pgpSignature.init(JcaPGPContentVerifierBuilderProvider.init().setProvider(Security.getProvider("BC")), pgpPublicKey);
	//also works: 
	//pgpSignature.init(BcPGPContentVerifierBuilderProvider.init(), pgpPublicKey);
					
		
	//the string we have signed: 'test'
	var signedBufferedInputStream;
	if(usefile){
	  signedBufferedInputStream = BufferedInputStream.init(FileInputStream.init(signedFile));
	}else{
	  signedBufferedInputStream = BufferedInputStream.init(ByteArrayInputStream.init(signedFile.toString().getBytes('UTF-8')));
	}
		
	var ch;
  while ((ch = signedBufferedInputStream.read()) >= 0)
  {
    //dump(JavaCast( "int", ch));
		pgpSignature.update(ch);
  }
     
	if(!usefile){
	  ByteArrayInputStream.close();
	}
	
	BufferedInputStream.close();      
	
	//alternative to looping BufferedInputStream....
	//pgpSignature.update(signedFileInputStream);
	
	
	//returns true or false
	return pgpSignature.verify();
	
		
}

//2 possible calling methods: either from string or from files:

dump(label='verify result',var=verify(signedFile, sig, pubkey, false));

//uses pub key:99656C36B79A57E7 ecc with hash 512
//dump(label='verify result',var=verify('/var/www/vhosts/******/www/test/encryption/test2.txt', '/var/www/vhosts/******/www/test/encryption/sig2.asc', '/var/www/vhosts/******/www/test/encryption/pubkey2.asc', true));

</cfscript>
<cfcatch><cfdump var="#cfcatch#"></cfcatch>
</cftry>
1 Like