public class MTCaptchaTokenDecoderAndChecker
extends java.lang.Object
Java code to Decrypt and Check MTCaptcha Verified Token
MTCaptcha: https://www.mtcaptcha.com
Ref: https://www.mtcaptcha.com/dev-guide-validate-token
license Apache 2.0
copyright MTCatpcha 2019
- Decode the MTCaptcha VerifiedToken (v1) locally using the PrivateKey, without making an API call
- Check if the token info is valid.
Example use: MTCaptchaTokenDecoderAndChecker decoder = new MTCaptchaTokenDecoderAndChecker(); DecodedMTTokenInfo di = null; boolean isSuccess = false; String[] expectedDomains = {"some.example.com", "another.example.com"}; String expectionAction = "login"; Boolean isProductionEnv = true; di = decoder.decodeMTToken(privatekey, token); if( di.decodeSuccess) { decoder.checkMTTokenSuccess(di, expectedDomains, expectionAction, isProductionEnv); isSuccess = di.checkSuccess; }
NOTE / WARNING:
Method checkTokenGUIDIsNotUsedAndMark()
is really for demonstration purpose and should be over written. The current impl is an acceptable single
server implementation, using local memory to track if a particular token (GUID) has been used before. For
applications where there are more than 1 (single) server instance, this should be replaced by
some check via shared cache (with expiration) such as MemCached / REDIS etc.
The class is Thread Safe
This code is for demonstration purpose and licensed under APACHE LICENSE 2.0
Sample Verified-Token String v1(2f03cc7d,1058dfde,MTPublic-hal9000uJ,34715559cd42d3955114303c925c3582,kSdkIYABYAKSmXze77v8oC1zCpBQJAOeCNaD8Q9ZnHTl3XTJ49KNll-FR3T-yzqE23CncDtF1o6IiyoCPEAeVnWshzllM0TqppHtp7KzGMJiUEApltXGHYlK6V2EasR-pNCaJo99k0W8tm5OR2kt5xefFH-cYypRRzIWzoppZMSntamR6SVYCotqfwKJ8OMb9WkYpoBV3e7_sjDUe-3_b_t55Sdf5CqmBkZWNkV0nbKdP9fngrmaDD3yJLkuUbKRBFySB7KHCgFgzVpzEQndCK0NcbFuuGbxbzYXmoxo8nKQsPVJB7s-vBu1Z5ZfD400bRfUTGoj8BH6w4RQD5qOCQ**) Verified-Token Structure "v1(" [MTCaptcha CheckSum] "," [Customer Checksum] "," [Sitekey] "," [Random Seed] "," [Encrypted TokenInfo] ")" [CalculatedCustomerCheckSum] = MD5( [Privatekey] + [SiteKey] + [Random Seed] + [Encrypted TokenInfo] ) .toHexLowercase() .substring(0,8) [EncryptedTokenInfoBinary] = URLSafeBase64.decode( [Encrypted TokenInfo].replace("*","=") ); [SingleUseDecryptionKey128bit] = MD5( [Privatekey] + [Random Seed] ) [AesIV] = [SingleUseDecryptionKey128bit] [DecryptedTokenInfoJson] = AES.decrpyt( "CBC/PKCS5Padding", [SingleUseDecryptionKey128bit], [AesIV], [EncryptedTokenInfoBinary] ) Sample TokenInfo Decrypted (JSON) { "v":"1.0", "code":201, "codeDesc":"valid:captcha-solved", "tokID":"34715559cd42d3955114303c925c3582", "timestampSec":981173106, "timestampISO":"2001-02-03T04:05:06Z", "hostname":"some.example.com", "isDevHost":false, "action":"", "ip":"10.10.10.10" }
Encryption/Decryption Algo: AES/CBC/PKCS5Padding with 128bit key
Hash Algo: MD5
Text to Binary Encoding: UTF8
SampleToken1String = v1(000eda01,eee7c778,MTPublic-hal9000uJ,4a774475f03ba00a2f122110af25461d,yCq1U1SO8fjrXGhcwRk8KWM9SFcOWWfYSwmgJHcbV_Uupa7bLOtXA5NaOaZQkMy0gLDWp72iVkizPTgy9HBFLihmXHUcLs2zHGjQXB1NoWObCWBNiKG3HcqIvSEbQNRfE6yig-vO5O1D3BPH7wdoUl_0YpzZZ4Vi1r--5IYVbZLmYa8Et1lKTHb7m9B40Zn1gspdO34wUYiWZX6WGmSBHSuCTe2-s4FOVTQh1-5qnfGUnWfZYpRN4zLvbnqFq3NpAL_PZvn0PyjNvCbmwv2K16GUCTxkm14nfVHTP_CovJoXJo7LV-arGFVFYixCnwzf4C5DHFJkfn76Kgy3wS1Eog**) SampleToken1Decrypted = {"v":"1.0","code":201,"codeDesc":"valid:captcha-solved","tokID":"34715559cd42d3955114303c925c3582","timestampSec":981173106,"timestampISO":"2001-02-03T04:05:06Z","hostname":"some.example.com","isDevHost":false,"action":"","ip":"10.10.10.10"} SampleToken2String = v1(980daee9,c265c978,MTPublic-hal9000uJ,495dbab6165529c22c38dfd3494bcfd5,n25YpNxDyzRURm_msNoW9bACoDg4HmqdXirSjqOfRSCuzwFKNI5z1L-KhHPe0hRz7tTIzjlFpHlkkdUYSlVZdxAAZq4_rkoCGUZ8FmngAr2-6t6EHXgD43l7AqyCReeReAkGeckV2eNfDzqToAC5epo0LBxJ7X0y-PcNIlseN4BPAbhFm5hV_9YhXGuXdWjqDxQSbqzwBXh2CjQ2893cRHAbFEyQzZShsiiubXdQYoY-jszt5DySVjnEQRFlzRnWT6H9gk6EioSX0U5BvSu1cH86Rfg1MwUSXpjYapt_eZWctp9VSWkDdPE1hw8hB6LVYHIjjrSvBqit8lrCpNRoNQ**) SampleToken2Decrypted = {"v":"1.0","code":211,"codeDesc":"valid:ip-whitelisted","tokID":"542de54b4ff00b5c3148802e10eeed4b","timestampSec":981173106,"timestampISO":"2001-02-03T04:05:06Z","hostname":"more.example.com","isDevHost":true,"action":"login","ip":"10.10.10.10"} Sample privatekey = MTPrivat-hal9000uJ-WsPXwe3BatWpGZaEbja2mcO5r7h1h1PkFW2fRoyGRrp4ZH6yfq Sample sitekey = MTPublic-hal9000uJ
DEPENDENCIES
- Google GSON 2.8.2+
- Apache Common Codec 1.11+
- Java 8
Constructor and Description |
---|
MTCaptchaTokenDecoderAndChecker() |
Modifier and Type | Method and Description |
---|---|
boolean |
checkMTTokenSuccess(DecodedMTTokenInfo di,
java.lang.String[] expectedDomains,
java.lang.String exepctedAction,
java.lang.Boolean isProductionEnv)
Checks if the DecodedMTTokenInfo is valid.
|
boolean |
checkTokenGUIDIsNotUsedAndMark(java.lang.String guid,
java.lang.Long tokenCreateTimeSec) |
DecodedMTTokenInfo |
decodeMTToken(java.lang.String privatekey,
java.lang.String token)
Decodes and Decrypts the MTCaptcha VerifiedToken
|
protected boolean |
decryptToken(DecodedMTTokenInfo di) |
protected byte[] |
getOneTimeEncryptionKey(java.lang.String privatekey,
java.lang.String randomSeed)
Generate 128bit / 16byte key for decryption,
As a MD5 Has of the privatekey string and the token's randomseed string
|
static void |
main(java.lang.String[] args)
Sample Running the code
|
static void |
runDemo() |
protected boolean |
unpackToken(DecodedMTTokenInfo di)
Parse and unpack the token into its components
|
protected boolean |
validateCustomerChecksum(DecodedMTTokenInfo di)
Validate the checksum matches and the token is not tampered
|
public boolean checkMTTokenSuccess(DecodedMTTokenInfo di, java.lang.String[] expectedDomains, java.lang.String exepctedAction, java.lang.Boolean isProductionEnv)
di
- The DecodedMTTokenInfo object, result from original decode callexpectedDomains
- The array of acceptable domains to check withexepctedAction
- The expected action, can be null if not setisProductionEnv
- Checks if token is generated under a Production or Development domain (configured by Sitekey)
The di.checkSuccess will be updated to true/false depending on the results of this check
The di.checkFailMsg will be updated if di.succcess==false
is Thread Safepublic boolean checkTokenGUIDIsNotUsedAndMark(java.lang.String guid, java.lang.Long tokenCreateTimeSec)
public DecodedMTTokenInfo decodeMTToken(java.lang.String privatekey, java.lang.String token)
privatekey
- The privatekeytoken
- The verified token stringprotected boolean decryptToken(DecodedMTTokenInfo di)
protected byte[] getOneTimeEncryptionKey(java.lang.String privatekey, java.lang.String randomSeed) throws java.security.NoSuchAlgorithmException
privatekey
- The privatekeyrandomSeed
- The randomseed string from the tokenjava.security.NoSuchAlgorithmException
- if MD5 digest is not foundprotected boolean validateCustomerChecksum(DecodedMTTokenInfo di)
di
- The DecodedMTTokenInfoprotected boolean unpackToken(DecodedMTTokenInfo di)
di
- The DecodedMTTokenInfopublic static void main(java.lang.String[] args)
args
- This is not usedpublic static void runDemo()