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()