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 four pieces of information separated by the | character:
phoneNumber|dateOfVerified|usedMethodName|verificationKey
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")
- verificationKey: A unique key for the verification instance used for server-side validation
Token Encryption
VerifySpeed encrypts the token using AES-256 encryption with the following process:
- Payload Creation: Combines the four 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
Verifying the Token
You must verify and 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 verification tools:
Important: The verify function throws an error if the token is older than 5 minutes.
- C#
- Node.js
- PHP
- Python
- Go
Package Installation:
dotnet add package VerifySpeed.VSCSharp --version 1.0.15
Usage:
using VerifySpeed.VSCSharp;
// Verify and decrypt the verification token
var result = token.VerifyVerificationToken(serverKey);
Console.WriteLine($"Phone: {result.PhoneNumber}");
Console.WriteLine($"Verification Key: {result.VerificationKey}");
Package Installation:
npm install @verifyspeed/vs-nodejs
Usage:
const { EncryptionTool } = require('@verifyspeed/vs-nodejs');
// Verify and decrypt the verification token
const result = EncryptionTool.verifyVerificationToken(token, serverKey);
console.log(`Phone: ${result.phoneNumber}`);
console.log(`Verification Key: ${result.verificationKey}`);
Package Installation:
composer require verifyspeed/vs-php
Usage:
use VerifySpeed\VSPHP\EncryptionTool;
// Verify and decrypt the verification token
$result = EncryptionTool::verifyVerificationToken($token, $serverKey);
echo "Phone: " . $result['phoneNumber'];
echo "\nVerification Key: " . $result['verificationKey'];
Package Installation:
pip install verifyspeed-vs-python
Usage:
from verifyspeed.vs_python import EncryptionTool
# Verify and decrypt the verification token
result = EncryptionTool.verify_verification_token(token, server_key)
print(f"Phone: {result['phoneNumber']}")
print(f"Verification Key: {result['verificationKey']}")
Package Installation:
go get github.com/verifyspeed/vs-go
Usage:
import "github.com/verifyspeed/vs-go"
// Verify and decrypt the verification token
result, err := vs.VerifyVerificationToken(token, serverKey)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Phone: %s\n", result.PhoneNumber)
fmt.Printf("Verification Key: %s\n", result.VerificationKey)
Manual Implementation
If you prefer to implement the verification 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>
/// Verifies and 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 verify and 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 VerifyVerificationToken(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 < 4)
{
throw new ArgumentException("The token format is invalid or corrupted");
}
string phoneNumber = parts[0];
DateTime dateOfVerification = DateTime.Parse(parts[1]);
string methodName = parts[2];
string verificationKey = parts[3];
// Reject tokens older than 5 minutes
if (dateOfVerification.AddMinutes(5) < DateTime.UtcNow)
{
throw new ArgumentException("The verification token has expired");
}
return new VerificationResult
{
PhoneNumber = phoneNumber,
DateOfVerification = dateOfVerification,
MethodName = methodName,
VerificationKey = verificationKey
};
}
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; }
public string VerificationKey { get; set; }
}
const crypto = require('crypto');
class EncryptionTool {
/**
* Verifies and decrypts an encrypted verification token using the specified server key
* @param {string} token - The Base64-encoded encrypted token string to verify and decrypt
* @param {string} serverKey - The server key used for AES decryption
* @returns {Object} An object containing the decrypted verification details
*/
static verifyVerificationToken(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 < 4) {
throw new Error('The token format is invalid or corrupted');
}
const result = {
phoneNumber: parts[0],
dateOfVerification: new Date(parts[1]),
methodName: parts[2],
verificationKey: parts[3]
};
// Reject tokens older than 5 minutes
const tokenAgeMinutes = (Date.now() - result.dateOfVerification.getTime()) / (1000 * 60);
if (tokenAgeMinutes > 5) {
throw new Error('The verification token has expired');
}
return result;
} 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.verifyVerificationToken(encryptedToken, serverKey);
console.log('Phone Number:', result.phoneNumber);
console.log('Verification Date:', result.dateOfVerification);
console.log('Method Used:', result.methodName);
console.log('Verification Key:', result.verificationKey);
} catch (error) {
console.error('Error decrypting token:', error.message);
}
<?php
class EncryptionTool
{
/**
* Verifies and decrypts an encrypted verification token using the specified server key
*
* @param string $token The Base64-encoded encrypted token string to verify and 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 verifyVerificationToken($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) < 4) {
throw new Exception('The token format is invalid or corrupted');
}
$result = [
'phoneNumber' => $parts[0],
'dateOfVerification' => new DateTime($parts[1]),
'methodName' => $parts[2],
'verificationKey' => $parts[3]
];
// Reject tokens older than 5 minutes
$tokenAgeMinutes = (time() - $result['dateOfVerification']->getTimestamp()) / 60;
if ($tokenAgeMinutes > 5) {
throw new Exception('The verification token has expired');
}
return $result;
} 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::verifyVerificationToken($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";
echo "Verification Key: " . $result['verificationKey'] . "\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 verify_verification_token(token, server_key):
"""
Verifies and decrypts an encrypted verification token using the specified server key
Args:
token (str): The Base64-encoded encrypted token string to verify and 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) < 4:
raise ValueError("The token format is invalid or corrupted")
result = {
'phoneNumber': parts[0],
'dateOfVerification': datetime.fromisoformat(parts[1]),
'methodName': parts[2],
'verificationKey': parts[3]
}
# Reject tokens older than 5 minutes
token_age_minutes = (datetime.now() - result['dateOfVerification']).total_seconds() / 60
if token_age_minutes > 5:
raise ValueError("The verification token has expired")
return result
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.verify_verification_token(encrypted_token, server_key)
print(f"Phone Number: {result['phoneNumber']}")
print(f"Verification Date: {result['dateOfVerification']}")
print(f"Method Used: {result['methodName']}")
print(f"Verification Key: {result['verificationKey']}")
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
VerificationKey string
}
type EncryptionTool struct{}
// VerifyVerificationToken verifies and decrypts an encrypted verification token using the specified server key
// and returns the corresponding VerificationResult struct.
func (e *EncryptionTool) VerifyVerificationToken(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) < 4 {
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)
}
// Reject tokens older than 5 minutes
if time.Since(dateOfVerification) > 5*time.Minute {
return nil, errors.New("the verification token has expired")
}
return &VerificationResult{
PhoneNumber: parts[0],
DateOfVerification: dateOfVerification,
MethodName: parts[2],
VerificationKey: parts[3],
}, 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.VerifyVerificationToken(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)
fmt.Printf("Verification Key: %s\n", result.VerificationKey)
}
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 Verification: Verify and decrypt token using your server key (reject if older than 5 minutes)
- 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: The verify function throws when the token is older than 5 minutes
Always implement proper error handling to gracefully manage these scenarios in your application.
Token Expiry Validation
Default Security: All official packages automatically validate that verification tokens are no older than 5 minutes. If a token is older than 5 minutes, the packages will throw an exception, preventing replay attacks and ensuring the verification is recent and valid.
Additional Security: If you need stricter security requirements (e.g., only accepting tokens that are no more than 2 minutes old), you can implement additional validation by checking the dateOfVerification field after decryption.
Here's an example of how to implement custom token expiry validation:
- C#
- Node.js
- PHP
- Python
- Go
// First, verify the token using the official package (validates 5-minute expiry)
var result = token.VerifyVerificationToken(serverKey);
// Additional security: Check if token is no more than 2 minutes old
var tokenAge = DateTime.UtcNow - result.DateOfVerification;
if (tokenAge.TotalMinutes > 2)
{
throw new ArgumentException($"Token is too old for this operation. Age: {tokenAge.TotalMinutes:F1} minutes, Maximum allowed: 2 minutes");
}
// Token is valid and meets additional security requirements
Console.WriteLine($"Phone: {result.PhoneNumber}");
Console.WriteLine($"Verification Key: {result.VerificationKey}");
// First, verify the token using the official package (validates 5-minute expiry)
const result = EncryptionTool.verifyVerificationToken(token, serverKey);
// Additional security: Check if token is no more than 2 minutes old
const tokenAge = (Date.now() - result.dateOfVerification.getTime()) / (1000 * 60);
if (tokenAge > 2) {
throw new Error(`Token is too old for this operation. Age: ${tokenAge.toFixed(1)} minutes, Maximum allowed: 2 minutes`);
}
// Token is valid and meets additional security requirements
console.log(`Phone: ${result.phoneNumber}`);
console.log(`Verification Key: ${result.verificationKey}`);
// First, verify the token using the official package (validates 5-minute expiry)
$result = EncryptionTool::verifyVerificationToken($token, $serverKey);
// Additional security: Check if token is no more than 2 minutes old
$tokenAge = (time() - $result['dateOfVerification']->getTimestamp()) / 60;
if ($tokenAge > 2) {
throw new Exception("Token is too old for this operation. Age: " . round($tokenAge, 1) . " minutes, Maximum allowed: 2 minutes");
}
// Token is valid and meets additional security requirements
echo "Phone: " . $result['phoneNumber'] . "\n";
echo "Verification Key: " . $result['verificationKey'] . "\n";
# First, verify the token using the official package (validates 5-minute expiry)
result = EncryptionTool.verify_verification_token(token, server_key)
# Additional security: Check if token is no more than 2 minutes old
token_age = (datetime.now() - result['dateOfVerification']).total_seconds() / 60
if token_age > 2:
raise ValueError(f"Token is too old for this operation. Age: {token_age:.1f} minutes, Maximum allowed: 2 minutes")
# Token is valid and meets additional security requirements
print(f"Phone: {result['phoneNumber']}")
print(f"Verification Key: {result['verificationKey']}")
// First, verify the token using the official package (validates 5-minute expiry)
result, err := vs.VerifyVerificationToken(token, serverKey)
if err != nil {
log.Fatal(err)
}
// Additional security: Check if token is no more than 2 minutes old
tokenAge := time.Since(result.DateOfVerification).Minutes()
if tokenAge > 2 {
log.Fatal(fmt.Errorf("token is too old for this operation. Age: %.1f minutes, Maximum allowed: 2 minutes", tokenAge))
}
// Token is valid and meets additional security requirements
fmt.Printf("Phone: %s\n", result.PhoneNumber)
fmt.Printf("Verification Key: %s\n", result.VerificationKey)