1.2 Verification Result
Verification Result
After a successful verification, VerifySpeed provides an encrypted token that contains the verification details. This token must be decrypted on your server using your server key to extract the verified phone number and other information.
Token Structure
The verification token contains three pieces of information separated by the |
character:
phoneNumber|dateOfVerified|usedMethodName
Components:
- phoneNumber: The verified phone number
- dateOfVerified: The date and time when verification was completed
- usedMethodName: The verification method that was used (e.g., "WhatsApp Message", "Telegram OTP", "SMS OTP")
Token Encryption
VerifySpeed encrypts the token using AES-256 encryption with the following process:
- Payload Creation: Combines the three components with
|
separator - Key Derivation: Uses SHA256 hash of your server key as the AES key
- Encryption: Applies AES-256 encryption with a random IV
- Encoding: Returns the result as a Base64-encoded string
Decrypting the Token
You must decrypt the token on your server using your server key to access the verification details. We provide official packages for each programming language that contain the decryption tools:
- C#
- Node.js
- PHP
- Python
- Go
Package Installation:
dotnet add package VerifySpeed.VSCSharp --version 1.0.15
Usage:
using VerifySpeed.VSCSharp;
// Decrypt the verification token
var result = token.DecryptVerificationToken(serverKey);
Console.WriteLine($"Phone: {result.PhoneNumber}");
Package Installation:
npm install @verifyspeed/vs-nodejs
Usage:
const { EncryptionTool } = require('@verifyspeed/vs-nodejs');
// Decrypt the verification token
const result = EncryptionTool.decryptVerificationToken(token, serverKey);
console.log(`Phone: ${result.phoneNumber}`);
Package Installation:
composer require verifyspeed/vs-php
Usage:
use VerifySpeed\VSPHP\EncryptionTool;
// Decrypt the verification token
$result = EncryptionTool::decryptVerificationToken($token, $serverKey);
echo "Phone: " . $result['phoneNumber'];
Package Installation:
pip install verifyspeed-vs-python
Usage:
from verifyspeed.vs_python import EncryptionTool
# Decrypt the verification token
result = EncryptionTool.decrypt_verification_token(token, server_key)
print(f"Phone: {result['phoneNumber']}")
Package Installation:
go get github.com/verifyspeed/vs-go
Usage:
import "github.com/verifyspeed/vs-go"
// Decrypt the verification token
result, err := vs.DecryptVerificationToken(token, serverKey)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Phone: %s\n", result.PhoneNumber)
Manual Implementation
If you prefer to implement the decryption manually or need to customize the behavior, here are the complete code examples:
- C#
- Node.js
- PHP
- Python
- Go
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public static class EncryptionTool
{
/// <summary>
/// Decrypts an encrypted verification token using the specified server key
/// and returns the corresponding VerificationResult object.
/// </summary>
/// <param name="token">The Base64-encoded encrypted token string to decrypt.</param>
/// <param name="serverKey">The server key used for AES decryption.</param>
/// <returns>A VerificationResult object representing the decrypted token.</returns>
public static VerificationResult DecryptVerificationToken(this string token, string serverKey)
{
if (string.IsNullOrWhiteSpace(token))
{
throw new ArgumentException("The verification token cannot be null or empty");
}
string decrypted = Decrypt(token, serverKey);
string[] parts = decrypted.Split('|');
if (parts.Length != 3)
{
throw new ArgumentException("The token format is invalid or corrupted");
}
string phoneNumber = parts[0];
DateTime dateOfVerification = DateTime.Parse(parts[1]);
string methodName = parts[2];
return new VerificationResult
{
PhoneNumber = phoneNumber,
DateOfVerification = dateOfVerification,
MethodName = methodName
};
}
private static string Decrypt(string token, string serverKey)
{
try
{
byte[] cipherBytes = Convert.FromBase64String(token);
using var aes = Aes.Create();
aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(serverKey));
using var memoryStream = new MemoryStream(cipherBytes);
var iv = new byte[aes.BlockSize / 8];
_ = memoryStream.Read(iv);
aes.IV = iv;
using var cryptoStream = new CryptoStream(
memoryStream,
aes.CreateDecryptor(),
CryptoStreamMode.Read
);
using var streamReader = new StreamReader(cryptoStream);
return streamReader.ReadToEnd();
}
catch (Exception ex)
{
throw new Exception("Failed to decrypt the verification token", ex);
}
}
}
public class VerificationResult
{
public string PhoneNumber { get; set; }
public DateTime DateOfVerification { get; set; }
public string MethodName { get; set; }
}
const crypto = require('crypto');
class EncryptionTool {
/**
* Decrypts an encrypted verification token using the specified server key
* @param {string} token - The Base64-encoded encrypted token string to decrypt
* @param {string} serverKey - The server key used for AES decryption
* @returns {Object} An object containing the decrypted verification details
*/
static decryptVerificationToken(token, serverKey) {
if (!token || !serverKey) {
throw new Error('Token and server key are required');
}
try {
const decrypted = this.decrypt(token, serverKey);
const parts = decrypted.split('|');
if (parts.length !== 3) {
throw new Error('The token format is invalid or corrupted');
}
return {
phoneNumber: parts[0],
dateOfVerification: new Date(parts[1]),
methodName: parts[2]
};
} catch (error) {
throw new Error(`Failed to decrypt verification token: ${error.message}`);
}
}
static decrypt(token, serverKey) {
try {
const cipherBytes = Buffer.from(token, 'base64');
const key = crypto.createHash('sha256').update(serverKey, 'utf8').digest();
// Extract IV (first 16 bytes)
const iv = cipherBytes.slice(0, 16);
const encryptedData = cipherBytes.slice(16);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encryptedData, null, 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
throw new Error(`Decryption failed: ${error.message}`);
}
}
}
// Usage example
try {
const result = EncryptionTool.decryptVerificationToken(encryptedToken, serverKey);
console.log('Phone Number:', result.phoneNumber);
console.log('Verification Date:', result.dateOfVerification);
console.log('Method Used:', result.methodName);
} catch (error) {
console.error('Error decrypting token:', error.message);
}
<?php
class EncryptionTool
{
/**
* Decrypts an encrypted verification token using the specified server key
*
* @param string $token The Base64-encoded encrypted token string to decrypt
* @param string $serverKey The server key used for AES decryption
* @return array An array containing the decrypted verification details
* @throws Exception When decryption fails or token format is invalid
*/
public static function decryptVerificationToken($token, $serverKey)
{
if (empty($token) || empty($serverKey)) {
throw new Exception('Token and server key are required');
}
try {
$decrypted = self::decrypt($token, $serverKey);
$parts = explode('|', $decrypted);
if (count($parts) !== 3) {
throw new Exception('The token format is invalid or corrupted');
}
return [
'phoneNumber' => $parts[0],
'dateOfVerification' => new DateTime($parts[1]),
'methodName' => $parts[2]
];
} catch (Exception $e) {
throw new Exception('Failed to decrypt verification token: ' . $e->getMessage());
}
}
private static function decrypt($token, $serverKey)
{
try {
$cipherBytes = base64_decode($token);
$key = hash('sha256', $serverKey, true);
// Extract IV (first 16 bytes)
$iv = substr($cipherBytes, 0, 16);
$encryptedData = substr($cipherBytes, 16);
$decrypted = openssl_decrypt($encryptedData, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
if ($decrypted === false) {
throw new Exception('Decryption failed');
}
return $decrypted;
} catch (Exception $e) {
throw new Exception('Decryption failed: ' . $e->getMessage());
}
}
}
// Usage example
try {
$result = EncryptionTool::decryptVerificationToken($encryptedToken, $serverKey);
echo "Phone Number: " . $result['phoneNumber'] . "\n";
echo "Verification Date: " . $result['dateOfVerification']->format('Y-m-d H:i:s') . "\n";
echo "Method Used: " . $result['methodName'] . "\n";
} catch (Exception $e) {
echo "Error decrypting token: " . $e->getMessage() . "\n";
}
?>
import base64
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from datetime import datetime
class EncryptionTool:
@staticmethod
def decrypt_verification_token(token, server_key):
"""
Decrypts an encrypted verification token using the specified server key
Args:
token (str): The Base64-encoded encrypted token string to decrypt
server_key (str): The server key used for AES decryption
Returns:
dict: A dictionary containing the decrypted verification details
Raises:
ValueError: When decryption fails or token format is invalid
"""
if not token or not server_key:
raise ValueError("Token and server key are required")
try:
decrypted = EncryptionTool._decrypt(token, server_key)
parts = decrypted.split('|')
if len(parts) != 3:
raise ValueError("The token format is invalid or corrupted")
return {
'phoneNumber': parts[0],
'dateOfVerification': datetime.fromisoformat(parts[1]),
'methodName': parts[2]
}
except Exception as e:
raise ValueError(f"Failed to decrypt verification token: {str(e)}")
@staticmethod
def _decrypt(token, server_key):
try:
cipher_bytes = base64.b64decode(token)
key = hashlib.sha256(server_key.encode('utf-8')).digest()
# Extract IV (first 16 bytes)
iv = cipher_bytes[:16]
encrypted_data = cipher_bytes[16:]
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(encrypted_data)
# Remove padding
unpadded = unpad(decrypted, AES.block_size)
return unpadded.decode('utf-8')
except Exception as e:
raise ValueError(f"Decryption failed: {str(e)}")
# Usage example
try:
result = EncryptionTool.decrypt_verification_token(encrypted_token, server_key)
print(f"Phone Number: {result['phoneNumber']}")
print(f"Verification Date: {result['dateOfVerification']}")
print(f"Method Used: {result['methodName']}")
except ValueError as e:
print(f"Error decrypting token: {e}")
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"log"
"strings"
"time"
)
type VerificationResult struct {
PhoneNumber string
DateOfVerification time.Time
MethodName string
}
type EncryptionTool struct{}
// DecryptVerificationToken decrypts an encrypted verification token using the specified server key
// and returns the corresponding VerificationResult struct.
func (e *EncryptionTool) DecryptVerificationToken(token, serverKey string) (*VerificationResult, error) {
if token == "" || serverKey == "" {
return nil, errors.New("token and server key are required")
}
decrypted, err := e.decrypt(token, serverKey)
if err != nil {
return nil, fmt.Errorf("failed to decrypt verification token: %w", err)
}
parts := strings.Split(decrypted, "|")
if len(parts) != 3 {
return nil, errors.New("the token format is invalid or corrupted")
}
dateOfVerification, err := time.Parse(time.RFC3339, parts[1])
if err != nil {
return nil, fmt.Errorf("invalid verification date: %w", err)
}
return &VerificationResult{
PhoneNumber: parts[0],
DateOfVerification: dateOfVerification,
MethodName: parts[2],
}, nil
}
func (e *EncryptionTool) decrypt(token, serverKey string) (string, error) {
cipherBytes, err := base64.StdEncoding.DecodeString(token)
if err != nil {
return "", fmt.Errorf("invalid base64 encoding: %w", err)
}
// Generate key from server key using SHA256
key := sha256.Sum256([]byte(serverKey))
// Extract IV (first 16 bytes)
if len(cipherBytes) < aes.BlockSize {
return "", errors.New("cipher text too short")
}
iv := cipherBytes[:aes.BlockSize]
ciphertext := cipherBytes[aes.BlockSize:]
block, err := aes.NewCipher(key[:])
if err != nil {
return "", fmt.Errorf("failed to create cipher: %w", err)
}
if len(ciphertext)%aes.BlockSize != 0 {
return "", errors.New("ciphertext is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(ciphertext, ciphertext)
// Remove PKCS7 padding
padding := int(ciphertext[len(ciphertext)-1])
if padding > aes.BlockSize || padding == 0 {
return "", errors.New("invalid padding size")
}
for i := len(ciphertext) - padding; i < len(ciphertext); i++ {
if ciphertext[i] != byte(padding) {
return "", errors.New("invalid padding")
}
}
return string(ciphertext[:len(ciphertext)-padding]), nil
}
// Usage example
func main() {
encryptionTool := &EncryptionTool{}
result, err := encryptionTool.DecryptVerificationToken(encryptedToken, serverKey)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Phone Number: %s\n", result.PhoneNumber)
fmt.Printf("Verification Date: %s\n", result.DateOfVerification.Format("2006-01-02 15:04:05"))
fmt.Printf("Method Used: %s\n", result.MethodName)
}
Security Considerations
- Server Key Protection: Keep your server key secure and never expose it in client-side code
- Token Validation: Always validate the decrypted data before using it in your application
- Error Handling: Implement proper error handling for decryption failures
- Token Expiry: Consider implementing token expiry validation based on the verification date
Integration Flow
- Client Side: User completes verification and receives encrypted token
- Client to Server: Send encrypted token to your backend
- Server Decryption: Decrypt token using your server key
- Data Extraction: Extract phone number and verification details
- Application Logic: Use the verified phone number in your application
Error Handling
Common errors you may encounter:
- Invalid Token Format: Token is corrupted or malformed
- Decryption Failure: Server key mismatch or token corruption
- Missing Data: Required token components are missing
- Invalid Date: Verification date cannot be parsed
- Token Expired: Token verification date is too old (recommended: reject tokens older than 5 minutes)
Always implement proper error handling to gracefully manage these scenarios in your application.
Token Expiry Validation
Security Recommendation: Never accept verification tokens where the date of verification is more than 5 minutes old. This prevents replay attacks and ensures the verification is recent and valid.
Here's an example of how to implement token expiry validation:
- C#
- Node.js
- PHP
- Python
- Go
public static VerificationResult DecryptVerificationToken(this string token, string serverKey, int maxAgeMinutes = 5)
{
// ... existing decryption code ...
var result = new VerificationResult
{
PhoneNumber = phoneNumber,
DateOfVerification = dateOfVerification,
MethodName = methodName
};
// Validate token age
var tokenAge = DateTime.UtcNow - dateOfVerification;
if (tokenAge.TotalMinutes > maxAgeMinutes)
{
throw new ArgumentException($"Token is too old. Age: {tokenAge.TotalMinutes:F1} minutes, Maximum allowed: {maxAgeMinutes} minutes");
}
return result;
}
static decryptVerificationToken(token, serverKey, maxAgeMinutes = 5) {
// ... existing decryption code ...
const result = {
phoneNumber: parts[0],
dateOfVerification: new Date(parts[1]),
methodName: parts[2]
};
// Validate token age
const tokenAge = (Date.now() - result.dateOfVerification.getTime()) / (1000 * 60);
if (tokenAge > maxAgeMinutes) {
throw new Error(`Token is too old. Age: ${tokenAge.toFixed(1)} minutes, Maximum allowed: ${maxAgeMinutes} minutes`);
}
return result;
}
public static function decryptVerificationToken($token, $serverKey, $maxAgeMinutes = 5)
{
// ... existing decryption code ...
$result = [
'phoneNumber' => $parts[0],
'dateOfVerification' => new DateTime($parts[1]),
'methodName' => $parts[2]
];
// Validate token age
$tokenAge = (time() - $result['dateOfVerification']->getTimestamp()) / 60;
if ($tokenAge > $maxAgeMinutes) {
throw new Exception("Token is too old. Age: " . round($tokenAge, 1) . " minutes, Maximum allowed: {$maxAgeMinutes} minutes");
}
return $result;
}
@staticmethod
def decrypt_verification_token(token, server_key, max_age_minutes=5):
# ... existing decryption code ...
result = {
'phoneNumber': parts[0],
'dateOfVerification': datetime.fromisoformat(parts[1]),
'methodName': parts[2]
}
// Validate token age
token_age = (datetime.now() - result['dateOfVerification']).total_seconds() / 60
if token_age > max_age_minutes:
raise ValueError(f"Token is too old. Age: {token_age:.1f} minutes, Maximum allowed: {max_age_minutes} minutes")
return result
func (e *EncryptionTool) DecryptVerificationToken(token, serverKey string, maxAgeMinutes int) (*VerificationResult, error) {
// ... existing decryption code ...
result := &VerificationResult{
PhoneNumber: parts[0],
DateOfVerification: dateOfVerification,
MethodName: parts[2],
}
// Validate token age
tokenAge := time.Since(dateOfVerification).Minutes()
if tokenAge > float64(maxAgeMinutes) {
return nil, fmt.Errorf("token is too old. Age: %.1f minutes, Maximum allowed: %d minutes", tokenAge, maxAgeMinutes)
}
return result, nil
}