CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyscard

Smartcard library for Python providing PC/SC interface for smart card communication

Pending
Overview
Eval results
Files

atr-card-types.mddocs/

ATR and Card Types

Tools for parsing Answer To Reset (ATR) sequences, extracting card parameters, and implementing card type detection logic. The ATR contains essential information about the card's capabilities and communication parameters.

Capabilities

ATR Processing

The ATR class provides comprehensive parsing and analysis of Answer To Reset sequences according to ISO/IEC 7816-3 standards.

class ATR:
    def __init__(self, atr):
        """
        Parse an Answer To Reset sequence.
        
        Args:
            atr (list[int]): ATR bytes as list of integers
        
        Raises:
            InvalidATRException: If ATR format is invalid
        """
    
    # ATR Structure Properties
    @property
    def TS(self):
        """int: Initial character indicating bit order and voltage class."""
    
    @property
    def T0(self):
        """int: Format character encoding interface characters presence and historical bytes count."""
    
    @property
    def K(self):
        """int: Number of historical bytes."""
    
    @property
    def TA(self):
        """list[int]: Interface characters TA1, TA2, ..."""
    
    @property
    def TB(self):
        """list[int]: Interface characters TB1, TB2, ..."""
    
    @property
    def TC(self):
        """list[int]: Interface characters TC1, TC2, ..."""
    
    @property
    def TD(self):
        """list[int]: Interface characters TD1, TD2, ..."""
    
    @property
    def Y(self):
        """list[int]: Interface character presence indicators."""
    
    @property
    def historicalBytes(self):
        """list[int]: Historical bytes containing card-specific information."""
    
    @property
    def TCK(self):
        """int: Check character for ATR integrity verification."""
    
    # Protocol Detection
    def getSupportedProtocols(self):
        """
        Get dictionary of communication protocols supported by the card.
        
        Returns:
            dict[str, bool]: Dictionary with protocol names as keys (e.g., "T=0", "T=1") 
                           and True as values for supported protocols
        """
    
    def isT0Supported(self):
        """
        Check if T=0 protocol is supported.
        
        Returns:
            bool: True if T=0 is supported
        """
    
    def isT1Supported(self):
        """
        Check if T=1 protocol is supported.
        
        Returns:
            bool: True if T=1 is supported
        """
    
    def isT15Supported(self):
        """
        Check if T=15 protocol is supported.
        
        Returns:
            bool: True if T=15 is supported
        """
    
    # ATR Data Access
    def getChecksum(self):
        """
        Get the ATR checksum (TCK).
        
        Returns:
            int: Checksum byte, or None if not present
        """
    
    def getHistoricalBytes(self):
        """
        Get the historical bytes.
        
        Returns:
            list[int]: Historical bytes
        """
    
    def getHistoricalBytesCount(self):
        """
        Get the number of historical bytes.
        
        Returns:
            int: Count of historical bytes
        """
    
    # Communication Parameters  
    def getBitRateFactor(self):
        """
        Get the bit rate adjustment factor from TA1.
        
        Returns:
            int: Bit rate factor (1-16)
        """
    
    def getClockRateConversion(self):
        """
        Get the clock rate conversion factor from TA1.
        
        Returns:
            int: Clock rate conversion factor
        """
    
    def getProgrammingCurrent(self):
        """
        Get the maximum programming current from TB1.
        
        Returns:
            int: Programming current in mA, or None if not specified
        """
    
    def getProgrammingVoltage(self):
        """
        Get the programming voltage from TB1.
        
        Returns:
            int: Programming voltage class, or None if not specified
        """
    
    def getGuardTime(self):
        """
        Get the guard time from TC1.
        
        Returns:
            int: Guard time in ETUs (Elementary Time Units)
        """
    
    # Display and Formatting
    def render(self):
        """
        Render ATR in human-readable format with detailed breakdown.
        
        Returns:
            str: Formatted ATR analysis
        """
    
    def __str__(self):
        """
        String representation of ATR.
        
        Returns:
            str: ATR as space-separated hex bytes
        """
    
    def dump(self):
        """
        Deprecated: Use render() instead.
        
        Returns:
            str: ATR dump (deprecated)
        """
    
    # Class Constants for Parameter Lookup
    clockrateconversion = {
        # Clock rate conversion factors indexed by TA1 high nibble
        0x0: 372, 0x1: 372, 0x2: 558, 0x3: 744, 0x4: 1116, 0x5: 1488,
        0x6: 1860, 0x7: 2232, 0x8: 2604, 0x9: 372, 0xA: 512, 0xB: 768,  
        0xC: 1024, 0xD: 1536, 0xE: 2048, 0xF: 2048
    }
    
    bitratefactor = {
        # Bit rate adjustment factors indexed by TA1 low nibble  
        0x1: 1, 0x2: 2, 0x3: 4, 0x4: 8, 0x5: 16, 0x6: 32,
        0x8: 12, 0x9: 20
    }
    
    currenttable = {
        # Programming current values in mA indexed by TB1 low nibble
        0x0: 25, 0x1: 50, 0x2: 100, 0x3: 200, 0x4: 400, 0x5: 800
    }

Card Type Detection

Card type classes provide flexible mechanisms for matching cards based on ATR patterns, enabling applications to identify specific card types.

class CardType:
    """Abstract base class for card type matching."""
    
    def matches(self, atr, reader=None):
        """
        Check if the given ATR matches this card type.
        
        Args:
            atr (list[int]): ATR bytes to check
            reader (Reader, optional): Reader containing the card
            
        Returns:
            bool: True if ATR matches this card type
        """

class AnyCardType(CardType):
    """Card type that matches any card."""
    
    def matches(self, atr, reader=None):
        """
        Always returns True - matches any card type.
        
        Args:
            atr (list[int]): ATR bytes (ignored)
            reader (Reader, optional): Reader (ignored)
            
        Returns:
            bool: Always True
        """

class ATRCardType(CardType):
    """Card type that matches specific ATR patterns with optional masking."""
    
    def __init__(self, atr, mask=None):
        """
        Create ATR-based card type matcher.
        
        Args:
            atr (list[int]): ATR pattern to match
            mask (list[int], optional): Mask for ATR comparison. 
                                       If provided, must be same length as atr.
                                       0x00 bytes in mask are ignored during comparison.
                                       0xFF bytes in mask must match exactly.
        
        Raises:
            InvalidATRMaskLengthException: If mask length doesn't match ATR length
        """
    
    def matches(self, atr, reader=None):
        """
        Check if ATR matches the pattern with optional masking.
        
        Args:
            atr (list[int]): ATR bytes to check
            reader (Reader, optional): Reader containing the card
            
        Returns:
            bool: True if ATR matches pattern (considering mask if provided)
        """

Usage Examples

Basic ATR Analysis

from smartcard import ATR
from smartcard.util import toHexString

# Example ATR from a contact smart card
atr_bytes = [0x3B, 0x7F, 0x96, 0x00, 0x00, 0x80, 0x31, 0x80, 0x65, 0xB0, 0x84, 0x41, 0x82, 0x54, 0x43, 0x4F, 0x53, 0x70, 0x02, 0x47, 0x00, 0x44, 0x32, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]

# Parse the ATR
atr = ATR(atr_bytes)

print(f"ATR: {atr}")
print(f"ATR hex: {toHexString(atr_bytes)}")
print()

# Analyze structure
print("ATR Structure:")
print(f"  TS (Initial Character): 0x{atr.TS:02X}")
print(f"  T0 (Format Character): 0x{atr.T0:02X}")
print(f"  Historical Bytes Count: {atr.K}")
print(f"  Historical Bytes: {toHexString(atr.historicalBytes)}")
print()

# Check protocol support
print("Protocol Support:")
print(f"  T=0 supported: {atr.isT0Supported()}")
print(f"  T=1 supported: {atr.isT1Supported()}")
print(f"  T=15 supported: {atr.isT15Supported()}")
supported_protocols = atr.getSupportedProtocols()
print(f"  All supported protocols: {list(supported_protocols.keys())}")
print()

# Get communication parameters
print("Communication Parameters:")
if atr.TA:
    print(f"  Clock rate conversion: {atr.getClockRateConversion()}")
    print(f"  Bit rate factor: {atr.getBitRateFactor()}")
if atr.TB:
    current = atr.getProgrammingCurrent()
    voltage = atr.getProgrammingVoltage()
    if current: print(f"  Programming current: {current} mA")  
    if voltage: print(f"  Programming voltage class: {voltage}")
if atr.TC:
    print(f"  Guard time: {atr.getGuardTime()} ETU")

ATR Detailed Analysis

from smartcard import ATR

def analyze_atr(atr_bytes):
    """Perform comprehensive ATR analysis."""
    atr = ATR(atr_bytes)
    
    print("=== ATR Analysis ===")
    print(atr.render())  # Detailed human-readable breakdown
    print()
    
    # Interface character analysis
    print("Interface Characters:")
    for i, ta in enumerate(atr.TA, 1):
        print(f"  TA{i}: 0x{ta:02X}")
    for i, tb in enumerate(atr.TB, 1):
        print(f"  TB{i}: 0x{tb:02X}")
    for i, tc in enumerate(atr.TC, 1):
        print(f"  TC{i}: 0x{tc:02X}")
    for i, td in enumerate(atr.TD, 1):
        print(f"  TD{i}: 0x{td:02X}")
    
    return atr

# Example usage
sample_atr = [0x3B, 0x65, 0x00, 0x00, 0x9C, 0x11, 0x01, 0x01, 0x03]
analyze_atr(sample_atr)

Card Type Matching

from smartcard.CardType import ATRCardType, AnyCardType
from smartcard.CardRequest import CardRequest

# Match any card type
any_card = AnyCardType()
print(f"Any card matches sample ATR: {any_card.matches([0x3B, 0x65])}")

# Match specific ATR exactly
exact_atr = [0x3B, 0x65, 0x00, 0x00, 0x9C, 0x11, 0x01, 0x01, 0x03]
exact_card_type = ATRCardType(exact_atr)

test_atr1 = [0x3B, 0x65, 0x00, 0x00, 0x9C, 0x11, 0x01, 0x01, 0x03]  # Exact match
test_atr2 = [0x3B, 0x65, 0x00, 0x00, 0x9C, 0x11, 0x01, 0x01, 0x04]  # Different

print(f"Exact match: {exact_card_type.matches(test_atr1)}")  # True
print(f"Different ATR: {exact_card_type.matches(test_atr2)}")  # False

# Match with mask (ignore certain bytes)
atr_pattern = [0x3B, 0x65, 0x00, 0x00, 0x9C, 0x11, 0x01, 0x01, 0x03]
atr_mask =    [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00]  # Ignore last byte

masked_card_type = ATRCardType(atr_pattern, atr_mask)

test_atr3 = [0x3B, 0x65, 0x00, 0x00, 0x9C, 0x11, 0x01, 0x01, 0xFF]  # Last byte different but masked

print(f"Masked match: {masked_card_type.matches(test_atr3)}")  # True

Card Type in Card Request

from smartcard.CardType import ATRCardType
from smartcard.CardRequest import CardRequest
from smartcard import ATR

# Define card type for specific smart card series
mifare_atr_pattern = [0x3B, 0x8F, 0x80, 0x01, 0x80, 0x4F, 0x0C, 0xA0, 0x00, 0x00, 0x03, 0x06, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x6A]
mifare_card_type = ATRCardType(mifare_atr_pattern)

# Wait specifically for this card type
card_request = CardRequest(timeout=30, cardType=mifare_card_type)

try:
    print("Waiting for MIFARE card...")
    card_service = card_request.waitforcard()
    
    print("MIFARE card detected!")
    connection = card_service.connection
    
    # Analyze the ATR
    atr_bytes = connection.getATR()
    atr = ATR(atr_bytes)
    
    print(f"Card ATR: {atr}")
    print(f"Protocols: {list(atr.getSupportedProtocols().keys())}")
    print(f"Historical bytes: {atr.getHistoricalBytes()}")
    
except Exception as e:
    print(f"Card request failed: {e}")

Custom Card Type Implementation

from smartcard.CardType import CardType
from smartcard import ATR

class JavaCardType(CardType):
    """Custom card type for detecting Java Cards based on historical bytes."""
    
    def matches(self, atr_bytes, reader=None):
        try:
            atr = ATR(atr_bytes)
            historical = atr.getHistoricalBytes()
            
            # Check for Java Card indicators in historical bytes
            # This is a simplified example - real detection would be more complex
            if len(historical) >= 4:
                # Look for Java Card identifier
                if historical[0:3] == [0x4A, 0x43, 0x4F]:  # "JCO" in ASCII
                    return True
                    
                # Look for other Java Card patterns
                if 0x72 in historical:  # Common in Java Card ATRs
                    return True
                    
            return False
        except:
            return False

# Use custom card type
java_card_type = JavaCardType()
card_request = CardRequest(timeout=20, cardType=java_card_type)

try:
    print("Waiting for Java Card...")
    card_service = card_request.waitforcard()
    print("Java Card detected!")
    
except Exception as e:
    print(f"No Java Card found: {e}")

Related Types

# Exception types for ATR processing
class InvalidATRException(SmartcardException):
    """Raised when ATR format is invalid."""

class InvalidATRMaskLengthException(SmartcardException):
    """Raised when ATR mask length doesn't match ATR length."""

# Type aliases for clarity
ATRBytes = list[int]
ATRMask = list[int]
ProtocolDict = dict[str, bool]
HistoricalBytes = list[int]

# ATR parameter constants (from ATR class)
ClockRateConversion = dict[int, int]
BitRateFactor = dict[int, int]  
CurrentTable = dict[int, int]

Install with Tessl CLI

npx tessl i tessl/pypi-pyscard

docs

atr-card-types.md

card-connections.md

gui-components.md

index.md

monitoring.md

pcsc-interface.md

reader-management.md

session-api.md

status-word-handling.md

utilities.md

tile.json