by Guest » 22 Dec 2024, 22:16
Ich implementieren einen Challenge-Response-Mechanismus für die PIN-Entsperrung mithilfe von SafeNet eTokens und IDPrime-Karten. Die Implementierung funktioniert einwandfrei mit IDPrime-Karten, aber wenn ich zu eTokens wechsle, schlägt sie fehl und gibt den Fehler „Falsches Passwort“ zurück.
Implementierungsdetails:
- Challenge-Abruf: Ich rufe die Challenge manuell vom SafeNet-Client ab.
- Eingabe & Antwort: Ich gebe die Herausforderung ein, berechne die Antwort mithilfe meiner Implementierung und stelle die Antwort dem SafeNet Authentication Client (SAC) zur Verfügung.
- Verwendete Hardware:
- IDPrime-Karten: Implementierung funktioniert korrekt.
- eTokens: Die Implementierung schlägt mit der Fehlermeldung „falsches Passwort“ fehl.
Codeausschnitte: Nachfolgend finden Sie die vollständige Implementierung, einschließlich des Unit-Tests und der ChallengeProcessor-Klasse, die die Challenge-Response-Logik mithilfe von CAPI für die 3DES-Verschlüsselung im ECB-Modus verwaltet.
Unit-Test: ProcessChallengeTest
using System;
using Xunit;
public class ChallengeProcessorTests
{
[Fact]
public void ProcessChallengeTest()
{
// Example challenge for testing (8 bytes)
string challenge = "45 56 C9 12 91 32 30 AE";
string adminPin = "130235735173480899673024522832173431700726442523";
// Process the challenge
byte[] response = ChallengeProcessor.ProcessChallenge(challenge, adminPin);
// Convert the response to a hex string for debugging
string responseHex = BitConverter.ToString(response).Replace("-", " ");
Console.WriteLine("Challenge Response: " + responseHex);
Assert.NotNull(response);
Assert.Equal(8, response.Length);
}
}
ChallengeProcessor-Klasse
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
public static class ChallengeProcessor
{
// Constants from the native code
private const int ADMIN_KEY_LENGTH = 24; // 3DES key length
private const int CHALLENGE_LENGTH = 8;
private const int DES_HEADER_LENGTH = 12; // From the native blob layout
// CryptoAPI constants
private const string MS_ENHANCED_PROV = "Microsoft Enhanced Cryptographic Provider V1.0";
private const uint PROV_RSA_FULL = 1;
private const uint CRYPT_VERIFYCONTEXT = 0xF0000000;
private const uint CALG_3DES = 0x00006603; // 3DES algorithm ID
private const uint KP_MODE = 4;
private const uint CRYPT_MODE_ECB = 1;
// P/Invoke declarations
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CryptAcquireContext(
out IntPtr phProv,
string pszContainer,
string pszProvider,
uint dwProvType,
uint dwFlags);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptImportKey(
IntPtr hProv,
byte[] pbData,
uint dwDataLen,
IntPtr hPubKey,
uint dwFlags,
out IntPtr phKey);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptSetKeyParam(
IntPtr hKey,
uint dwParam,
byte[] pbData,
uint dwFlags);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptEncrypt(
IntPtr hKey,
IntPtr hHash,
bool Final,
uint dwFlags,
[In, Out] byte[] pbData,
ref uint pdwDataLen,
uint dwBufLen);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptDestroyKey(IntPtr hKey);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags);
///
/// Processes the challenge using the adminPin to derive the key and replicates the native encryption logic.
///
/// The challenge as either an 8-byte array or a hex string "13 E3 93 80 F7 33 44 EE".
/// The admin PIN ("default", "random", or 48-char hex string).
/// The 8-byte response.
public static byte[] ProcessChallenge(object challenge, string adminPin)
{
byte[] challengeBytes = ConvertChallenge(challenge);
byte[] key = ParseAdminKey(adminPin);
return CalculateChallengeResponseNative(challengeBytes, key);
}
///
/// Mimics sc_parse_adminkey logic:
/// - "default": 24 zero bytes
/// - "random": 24 random bytes
/// - else: interpret as hex, must be 48 hex chars for 24 bytes
///
private static byte[] ParseAdminKey(string adminPin)
{
if (adminPin.Equals("default", StringComparison.OrdinalIgnoreCase))
{
return new byte[ADMIN_KEY_LENGTH];
}
if (adminPin.Equals("random", StringComparison.OrdinalIgnoreCase))
{
byte[] rndKey = new byte[ADMIN_KEY_LENGTH];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(rndKey);
}
return rndKey;
}
return ParseHexAdminKey(adminPin);
}
///
/// Parses a 48-char hex string into 24 bytes.
///
private static byte[] ParseHexAdminKey(string hexString)
{
string cleanHex = hexString.Replace(" ", "").Replace("\t", "").Replace("\n", "").Replace("\r", "");
if (cleanHex.Length != ADMIN_KEY_LENGTH * 2)
{
throw new ArgumentException($"Admin key hex must represent {ADMIN_KEY_LENGTH} bytes (48 hex chars).");
}
byte[] key = new byte[ADMIN_KEY_LENGTH];
for (int i = 0; i < ADMIN_KEY_LENGTH; i++)
{
string byteHex = cleanHex.Substring(i * 2, 2);
if (!byte.TryParse(byteHex, System.Globalization.NumberStyles.HexNumber, null, out key
))
{
throw new ArgumentException($"Invalid hex value '{byteHex}' in adminPin.");
}
}
return key;
}
///
/// Converts the challenge input into an 8-byte array.
/// If string: expects format like "13 E3 93 80 F7 33 44 EE".
/// If byte[]: just uses it directly.
///
private static byte[] ConvertChallenge(object challenge)
{
if (challenge is byte[] bytes)
{
if (bytes.Length != CHALLENGE_LENGTH)
throw new ArgumentException($"Challenge must be {CHALLENGE_LENGTH} bytes.");
return bytes;
}
if (challenge is string str)
{
var parts = str.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != CHALLENGE_LENGTH)
throw new ArgumentException($"Challenge must contain {CHALLENGE_LENGTH} hex bytes.");
byte[] c = new byte[CHALLENGE_LENGTH];
for (int i = 0; i < CHALLENGE_LENGTH; i++)
{
if (!byte.TryParse(parts, System.Globalization.NumberStyles.HexNumber, null, out c))
{
throw new ArgumentException($"Invalid hex byte '{parts}' in challenge.");
}
}
return c;
}
throw new ArgumentException("Challenge must be either a byte[] or a string.");
}
///
/// Replicates sc_calculate_challenge_response logic using CAPI:
/// - Acquire context
/// - Build 3DES key blob
/// - Import key
/// - Set mode to ECB
/// - Encrypt the challenge block
///
private static byte[] CalculateChallengeResponseNative(byte[] challenge, byte[] key)
{
if (challenge.Length != CHALLENGE_LENGTH)
throw new ArgumentException($"Challenge must be {CHALLENGE_LENGTH} bytes.");
if (key.Length != ADMIN_KEY_LENGTH)
throw new ArgumentException($"Key must be {ADMIN_KEY_LENGTH} bytes.");
// This blob structure is taken from the native code snippet
// It includes a key header for a 3DES key and space for the key
byte[] blob = new byte[]
{
0x08, 0x02, 0x00, 0x00, // Header (simple)
0x03, 0x66, 0x00, 0x00, // CALG_3DES = 0x00006603
0x18, 0x00, 0x00, 0x00, // 24-byte key length = 0x18
// 24 bytes of key data (initially zero)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00
};
// Copy the key bytes into the blob after the 12-byte header
Buffer.BlockCopy(key, 0, blob, DES_HEADER_LENGTH, ADMIN_KEY_LENGTH);
// Copy challenge into response buffer
byte[] response = new byte[CHALLENGE_LENGTH];
Array.Copy(challenge, response, CHALLENGE_LENGTH);
IntPtr hProv = IntPtr.Zero;
IntPtr hKey = IntPtr.Zero;
try
{
// Acquire cryptographic context
if (!CryptAcquireContext(out hProv, null, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
ThrowLastError("CryptAcquireContext failed");
// Import the 3DES key
if (!CryptImportKey(hProv, blob, (uint)blob.Length, IntPtr.Zero, 0, out hKey))
ThrowLastError("CryptImportKey failed");
// Set mode to ECB
byte[] mode = BitConverter.GetBytes((uint)CRYPT_MODE_ECB);
if (!CryptSetKeyParam(hKey, KP_MODE, mode, 0))
ThrowLastError("CryptSetKeyParam failed");
// Encrypt data in place
uint length = CHALLENGE_LENGTH;
if (!CryptEncrypt(hKey, IntPtr.Zero, false, 0, response, ref length, (uint)response.Length))
ThrowLastError("CryptEncrypt failed");
return response;
}
finally
{
if (hKey != IntPtr.Zero)
CryptDestroyKey(hKey);
if (hProv != IntPtr.Zero)
CryptReleaseContext(hProv, 0);
}
}
private static void ThrowLastError(string message)
{
int err = Marshal.GetLastWin32Error();
throw new InvalidOperationException($"{message}. Win32 Error: {err}");
}
}
Problem:
- < strong>Mit IDPrime-Karten: Die ProcessChallenge-Methode berechnet die Challenge-Antwort korrekt und die PIN-Entsperrung funktioniert wie erwartet.
- Mit eTokens: Dieselbe Methode gibt eine falsche Antwort zurück, was zu „falsch“ führt Passwort"-Fehler.
Fragen:
- Challenge-Berechnung: Gibt es einen Unterschied in der Art und Weise, wie die Challenge für eTokens im Vergleich zu IDPrime-Karten berechnet oder verarbeitet werden soll?
< /li>
Schlüsselhandhabung: Könnte es Unterschiede im Schlüssel geben? Ableitung oder Handhabung zwischen den beiden Arten von Token, die mir fehlen?
- Verschlüsselungsparameter: Gibt es bestimmte Verschlüsselungsparameter oder -modi, die bei der Arbeit mit eTokens angepasst werden müssen?
Irgendwelche Erkenntnisse oder Vorschläge zur korrekten Berechnung der Herausforderung für eTokens oder Unterschiede, die ich zwischen IDPrime Cards und berücksichtigen sollte eTokens wären sehr dankbar!
Ich implementieren einen Challenge-Response-Mechanismus für die PIN-Entsperrung mithilfe von SafeNet eTokens und IDPrime-Karten. Die Implementierung funktioniert einwandfrei mit IDPrime-Karten, aber wenn ich zu eTokens wechsle, schlägt sie fehl und gibt den Fehler „Falsches Passwort“ zurück.
[b]Implementierungsdetails:[/b]
[list]
[*][b]Challenge-Abruf:[/b] Ich rufe die Challenge manuell vom SafeNet-Client ab.
[*][b]Eingabe & Antwort:[/b] Ich gebe die Herausforderung ein, berechne die Antwort mithilfe meiner Implementierung und stelle die Antwort dem SafeNet Authentication Client (SAC) zur Verfügung.
[*] [b]Verwendete Hardware:[/b]
[/list]
[list]
[*] [b]IDPrime-Karten:[/b] Implementierung funktioniert korrekt.
[*][b]eTokens:[/b] Die Implementierung schlägt mit der Fehlermeldung „falsches Passwort“ fehl.
[/list]
[b]Codeausschnitte:[/b] Nachfolgend finden Sie die vollständige Implementierung, einschließlich des Unit-Tests und der ChallengeProcessor-Klasse, die die Challenge-Response-Logik mithilfe von CAPI für die 3DES-Verschlüsselung im ECB-Modus verwaltet.
Unit-Test: ProcessChallengeTest
using System;
using Xunit;
public class ChallengeProcessorTests
{
[Fact]
public void ProcessChallengeTest()
{
// Example challenge for testing (8 bytes)
string challenge = "45 56 C9 12 91 32 30 AE";
string adminPin = "130235735173480899673024522832173431700726442523";
// Process the challenge
byte[] response = ChallengeProcessor.ProcessChallenge(challenge, adminPin);
// Convert the response to a hex string for debugging
string responseHex = BitConverter.ToString(response).Replace("-", " ");
Console.WriteLine("Challenge Response: " + responseHex);
Assert.NotNull(response);
Assert.Equal(8, response.Length);
}
}
ChallengeProcessor-Klasse
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
public static class ChallengeProcessor
{
// Constants from the native code
private const int ADMIN_KEY_LENGTH = 24; // 3DES key length
private const int CHALLENGE_LENGTH = 8;
private const int DES_HEADER_LENGTH = 12; // From the native blob layout
// CryptoAPI constants
private const string MS_ENHANCED_PROV = "Microsoft Enhanced Cryptographic Provider V1.0";
private const uint PROV_RSA_FULL = 1;
private const uint CRYPT_VERIFYCONTEXT = 0xF0000000;
private const uint CALG_3DES = 0x00006603; // 3DES algorithm ID
private const uint KP_MODE = 4;
private const uint CRYPT_MODE_ECB = 1;
// P/Invoke declarations
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CryptAcquireContext(
out IntPtr phProv,
string pszContainer,
string pszProvider,
uint dwProvType,
uint dwFlags);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptImportKey(
IntPtr hProv,
byte[] pbData,
uint dwDataLen,
IntPtr hPubKey,
uint dwFlags,
out IntPtr phKey);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptSetKeyParam(
IntPtr hKey,
uint dwParam,
byte[] pbData,
uint dwFlags);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptEncrypt(
IntPtr hKey,
IntPtr hHash,
bool Final,
uint dwFlags,
[In, Out] byte[] pbData,
ref uint pdwDataLen,
uint dwBufLen);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptDestroyKey(IntPtr hKey);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags);
///
/// Processes the challenge using the adminPin to derive the key and replicates the native encryption logic.
///
/// The challenge as either an 8-byte array or a hex string "13 E3 93 80 F7 33 44 EE".
/// The admin PIN ("default", "random", or 48-char hex string).
/// The 8-byte response.
public static byte[] ProcessChallenge(object challenge, string adminPin)
{
byte[] challengeBytes = ConvertChallenge(challenge);
byte[] key = ParseAdminKey(adminPin);
return CalculateChallengeResponseNative(challengeBytes, key);
}
///
/// Mimics sc_parse_adminkey logic:
/// - "default": 24 zero bytes
/// - "random": 24 random bytes
/// - else: interpret as hex, must be 48 hex chars for 24 bytes
///
private static byte[] ParseAdminKey(string adminPin)
{
if (adminPin.Equals("default", StringComparison.OrdinalIgnoreCase))
{
return new byte[ADMIN_KEY_LENGTH];
}
if (adminPin.Equals("random", StringComparison.OrdinalIgnoreCase))
{
byte[] rndKey = new byte[ADMIN_KEY_LENGTH];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(rndKey);
}
return rndKey;
}
return ParseHexAdminKey(adminPin);
}
///
/// Parses a 48-char hex string into 24 bytes.
///
private static byte[] ParseHexAdminKey(string hexString)
{
string cleanHex = hexString.Replace(" ", "").Replace("\t", "").Replace("\n", "").Replace("\r", "");
if (cleanHex.Length != ADMIN_KEY_LENGTH * 2)
{
throw new ArgumentException($"Admin key hex must represent {ADMIN_KEY_LENGTH} bytes (48 hex chars).");
}
byte[] key = new byte[ADMIN_KEY_LENGTH];
for (int i = 0; i < ADMIN_KEY_LENGTH; i++)
{
string byteHex = cleanHex.Substring(i * 2, 2);
if (!byte.TryParse(byteHex, System.Globalization.NumberStyles.HexNumber, null, out key[i]))
{
throw new ArgumentException($"Invalid hex value '{byteHex}' in adminPin.");
}
}
return key;
}
///
/// Converts the challenge input into an 8-byte array.
/// If string: expects format like "13 E3 93 80 F7 33 44 EE".
/// If byte[]: just uses it directly.
///
private static byte[] ConvertChallenge(object challenge)
{
if (challenge is byte[] bytes)
{
if (bytes.Length != CHALLENGE_LENGTH)
throw new ArgumentException($"Challenge must be {CHALLENGE_LENGTH} bytes.");
return bytes;
}
if (challenge is string str)
{
var parts = str.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != CHALLENGE_LENGTH)
throw new ArgumentException($"Challenge must contain {CHALLENGE_LENGTH} hex bytes.");
byte[] c = new byte[CHALLENGE_LENGTH];
for (int i = 0; i < CHALLENGE_LENGTH; i++)
{
if (!byte.TryParse(parts[i], System.Globalization.NumberStyles.HexNumber, null, out c[i]))
{
throw new ArgumentException($"Invalid hex byte '{parts[i]}' in challenge.");
}
}
return c;
}
throw new ArgumentException("Challenge must be either a byte[] or a string.");
}
///
/// Replicates sc_calculate_challenge_response logic using CAPI:
/// - Acquire context
/// - Build 3DES key blob
/// - Import key
/// - Set mode to ECB
/// - Encrypt the challenge block
///
private static byte[] CalculateChallengeResponseNative(byte[] challenge, byte[] key)
{
if (challenge.Length != CHALLENGE_LENGTH)
throw new ArgumentException($"Challenge must be {CHALLENGE_LENGTH} bytes.");
if (key.Length != ADMIN_KEY_LENGTH)
throw new ArgumentException($"Key must be {ADMIN_KEY_LENGTH} bytes.");
// This blob structure is taken from the native code snippet
// It includes a key header for a 3DES key and space for the key
byte[] blob = new byte[]
{
0x08, 0x02, 0x00, 0x00, // Header (simple)
0x03, 0x66, 0x00, 0x00, // CALG_3DES = 0x00006603
0x18, 0x00, 0x00, 0x00, // 24-byte key length = 0x18
// 24 bytes of key data (initially zero)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00
};
// Copy the key bytes into the blob after the 12-byte header
Buffer.BlockCopy(key, 0, blob, DES_HEADER_LENGTH, ADMIN_KEY_LENGTH);
// Copy challenge into response buffer
byte[] response = new byte[CHALLENGE_LENGTH];
Array.Copy(challenge, response, CHALLENGE_LENGTH);
IntPtr hProv = IntPtr.Zero;
IntPtr hKey = IntPtr.Zero;
try
{
// Acquire cryptographic context
if (!CryptAcquireContext(out hProv, null, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
ThrowLastError("CryptAcquireContext failed");
// Import the 3DES key
if (!CryptImportKey(hProv, blob, (uint)blob.Length, IntPtr.Zero, 0, out hKey))
ThrowLastError("CryptImportKey failed");
// Set mode to ECB
byte[] mode = BitConverter.GetBytes((uint)CRYPT_MODE_ECB);
if (!CryptSetKeyParam(hKey, KP_MODE, mode, 0))
ThrowLastError("CryptSetKeyParam failed");
// Encrypt data in place
uint length = CHALLENGE_LENGTH;
if (!CryptEncrypt(hKey, IntPtr.Zero, false, 0, response, ref length, (uint)response.Length))
ThrowLastError("CryptEncrypt failed");
return response;
}
finally
{
if (hKey != IntPtr.Zero)
CryptDestroyKey(hKey);
if (hProv != IntPtr.Zero)
CryptReleaseContext(hProv, 0);
}
}
private static void ThrowLastError(string message)
{
int err = Marshal.GetLastWin32Error();
throw new InvalidOperationException($"{message}. Win32 Error: {err}");
}
}
[b]Problem:[/b]
[list]
[*]< strong>Mit IDPrime-Karten: Die ProcessChallenge-Methode berechnet die Challenge-Antwort korrekt und die PIN-Entsperrung funktioniert wie erwartet.
[*] [b]Mit eTokens:[/b] Dieselbe Methode gibt eine falsche Antwort zurück, was zu „falsch“ führt Passwort"-Fehler.
[/list]
[b]Fragen:[/b]
[list]
[*][b]Challenge-Berechnung:[/b] Gibt es einen Unterschied in der Art und Weise, wie die Challenge für eTokens im Vergleich zu IDPrime-Karten berechnet oder verarbeitet werden soll?
< /li>
[b]Schlüsselhandhabung:[/b] Könnte es Unterschiede im Schlüssel geben? Ableitung oder Handhabung zwischen den beiden Arten von Token, die mir fehlen?
[*][b]Verschlüsselungsparameter:[/b] Gibt es bestimmte Verschlüsselungsparameter oder -modi, die bei der Arbeit mit eTokens angepasst werden müssen?
[/list]
Irgendwelche Erkenntnisse oder Vorschläge zur korrekten Berechnung der Herausforderung für eTokens oder Unterschiede, die ich zwischen IDPrime Cards und berücksichtigen sollte eTokens wären sehr dankbar!