aurabx/jmix

PHP library for JMIX (JSON Medical Interchange) format - secure medical data exchange with cryptographic features

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/aurabx/jmix

0.4.1 2025-10-04 06:59 UTC

This package is auto-updated.

Last update: 2025-10-04 07:13:53 UTC


README

A PHP library for creating and processing JMIX (JSON Medical Interchange) envelopes from DICOM files. JMIX is a secure data format for exchanging medical/healthcare information with strong cryptographic features including AES-256-GCM encryption.

Features

  • Convert DICOM folders to complete JMIX envelopes
  • Automatic metadata extraction from DICOM files
  • JSON Schema validation for all components
  • Simple, array-based configuration
  • AES-256-GCM payload encryption with ECDH key exchange (Curve25519)
  • Payload decryption and envelope extraction
  • SHA-256 payload hash verification for data integrity
  • Built-in audit trail generation
  • CLI tools for building, analyzing, and decrypting envelopes
  • Ephemeral keys for forward secrecy

Requirements

In order to extract file information to build the files list, DCMTK is required. Without this, the library will not extract DICOM data.

# Install DCMTK on macOS
brew install dcmtk

# Install DCMTK on Ubuntu
apt-get install dcmtk

The library will automatically use dcmdump if available.

Installation

composer require aurabx/jmix

Quick Start

<?php
require_once 'vendor/autoload.php';

use AuraBox\Jmix\JmixBuilder;

// Configuration array
$config = [
    'sender' => [
        'name' => 'Radiology Clinic A',
        'id' => 'org:au.gov.health.123456',
        'contact' => 'imaging@clinica.org.au',
    ],
    'requester' => [
        'name' => 'Dr John Smith',
        'id' => 'org:au.gov.health.55555',
        'contact' => 'smith@clinicb.org.au',
    ],
    'receivers' => [[
        'name' => 'Radiology Clinic B',
        'id' => 'org:au.gov.health.987654',
        'contact' => ['system' => 'phone', 'value' => '+61049555555'],
    ]],
    'patient' => [
        'name' => 'Jane Doe',
        'dob' => '1975-02-14',
        'sex' => 'F',
        'ihi' => '8003608166690503',
    ],
];

// Build JMIX envelope
$jmixBuilder = new JmixBuilder();
$envelope = $jmixBuilder->buildFromDicom('/path/to/dicom/files', $config);

// Save to JMIX envelope directory
$envelopePath = $jmixBuilder->saveToFiles($envelope, '/path/to/output', $config);
echo "Envelope created at: {$envelopePath}\n";

// Example with encryption (optional)
// Generate keypair for encryption
use AuraBox\Jmix\Encryption\PayloadEncryptor;
$keypair = PayloadEncryptor::generateKeypair();

// Add encryption to config
$config['encryption'] = [
    'recipient_public_key' => $keypair['public_key']
];

// Build encrypted envelope
$encryptedEnvelope = $jmixBuilder->buildFromDicom('/path/to/dicom/files', $config);
$encryptedPath = $jmixBuilder->saveToFiles($encryptedEnvelope, '/path/to/output', $config);
echo "🔒 Encrypted envelope created at: {$encryptedPath}\n";

Encryption and Decryption

The library supports enterprise-grade AES-256-GCM encryption with ECDH key exchange for secure medical data transmission.

Generating Keys

use AuraBox\Jmix\Encryption\PayloadEncryptor;

// Generate a keypair for testing/development
$keypair = PayloadEncryptor::generateKeypair();
$publicKey = $keypair['public_key'];   // For encryption (share with sender)
$privateKey = $keypair['private_key']; // For decryption (keep secure!)

echo "Public Key: " . $publicKey . "\n";
echo "Private Key: " . $privateKey . "\n";

Creating Encrypted Envelopes

use AuraBox\Jmix\JmixBuilder;
use AuraBox\Jmix\Encryption\PayloadEncryptor;

// Your regular configuration
$config = [
    'sender' => ['name' => 'Clinic A', 'id' => 'org:clinic.a', 'contact' => 'info@clinica.com'],
    'requester' => ['name' => 'Dr Smith', 'id' => 'doc:smith', 'contact' => 'smith@clinic.com'],
    'receivers' => [['name' => 'Clinic B', 'id' => 'org:clinic.b', 'contact' => 'info@clinicb.com']],
    'patient' => ['name' => 'Jane Doe', 'dob' => '1975-02-14', 'sex' => 'F'],
    // ... other config
];

// Add encryption
$config['encryption'] = [
    'recipient_public_key' => $recipientPublicKey  // Recipient's public key
];

// Build encrypted envelope
$jmixBuilder = new JmixBuilder();
$envelope = $jmixBuilder->buildFromDicom('/path/to/dicom', $config);
$envelopePath = $jmixBuilder->saveToFiles($envelope, '/path/to/output', $config);

echo "🔒 Encrypted envelope created at: {$envelopePath}\n";

Analyzing and Decrypting Envelopes

use AuraBox\Jmix\JmixDecryptor;

$decryptor = new JmixDecryptor();

// 1. Analyze envelope (without extracting)
$analysis = $decryptor->analyzeEnvelope('/path/to/envelope.JMIX');
echo "Envelope ID: " . $analysis['envelope_id'] . "\n";
echo "Encrypted: " . ($analysis['is_encrypted'] ? 'Yes' : 'No') . "\n";
echo "Patient: " . $analysis['sender']['name'] . "\n";

if ($analysis['is_encrypted']) {
    echo "Encryption: " . $analysis['encryption']['algorithm'] . "\n";
}

// 2. Decrypt encrypted envelope
if ($analysis['is_encrypted']) {
    $envelope = $decryptor->decryptEnvelope(
        '/path/to/encrypted.JMIX',
        $privateKey,           // Your private key
        '/path/to/output'
    );
    
    echo "🔓 Decrypted envelope contents:\n";
    echo "Patient: " . $envelope['metadata']['patient']['name']['text'] . "\n";
    echo "DICOM files: " . $envelope['payload_path'] . '/dicom/' . "\n";
}

// 3. Extract unencrypted envelope
else {
    $envelope = $decryptor->extractEnvelope(
        '/path/to/unencrypted.JMIX',
        '/path/to/output'
    );
    
    echo "Extracted envelope contents:\n";
    echo "Patient: " . $envelope['metadata']['patient']['name']['text'] . "\n";
    echo "DICOM files: " . $envelope['payload_path'] . '/dicom/' . "\n";
}

CLI Tools

The library includes command-line tools for building and processing envelopes:

Building Envelopes

# Create unencrypted envelope
jmix-build /path/to/dicom config.json /path/to/output

# Create unencrypted envelope with a custom schema directory
jmix-build /path/to/dicom config.json /path/to/output /absolute/or/relative/path/to/schemas

# Create encrypted envelope (add encryption config to config.json)
jmix-build /path/to/dicom encrypted-config.json /path/to/output

Analyzing Envelopes

# Analyze any envelope (shows encryption status, metadata, etc.)
jmix-decrypt analyze /path/to/envelope.JMIX

# Analyze and verify cryptographic assertions
jmix-decrypt analyze /path/to/envelope.JMIX --verify-assertions

Output:

Envelope Analysis
ID: a1b2c3d4-5678-90ab-cdef-123456789abc
Timestamp: 2025-09-27T06:32:05Z
Encrypted: 🔒 Yes
Has Payload Hash: ✓ Yes

Sender:
  Name: Test Healthcare Organization
  ID: org:test.health.123

Encryption Details:
  Algorithm: AES-256-GCM
  Ephemeral Public Key: Y12JovXD3Hjc/mMk...

Extracting Unencrypted Envelopes

# Extract unencrypted envelope
jmix-decrypt extract /path/to/envelope.JMIX /path/to/output

Decrypting Encrypted Envelopes

# Decrypt encrypted envelope
jmix-decrypt decrypt /path/to/encrypted.JMIX /path/to/output <private-key-base64>

Output:

✓ Envelope decrypted successfully!

🔓 Decrypted Envelope Contents:
  ID: a1b2c3d4-5678-90ab-cdef-123456789abc
  Patient: Jane Doe
  Study: CT Pulmonary Angiogram
  Payload Path: /path/to/output/payload

📁 Extracted Files:
  - DICOM files in: /path/to/output/payload/dicom/
  - Attachment files in: /path/to/output/payload/files/

Configuration

Schema Validation and Schema Path

  • All generated JMIX components are validated against JSON Schemas: manifest.json, metadata.json, audit.json, and files.json (when present).
  • Default schema directory resolves to a sibling repository path: ../jmix/schemas (relative to this package).
  • You can override the schema path in both library and CLI:
    • Library: new JmixBuilder('/path/to/schemas') and new JmixDecryptor('/path/to/schemas')
    • CLI: jmix-build /dicom config.json /output /path/to/schemas

If you keep the JMIX schema repository checked out one directory up, the defaults will work out of the box.

Required Configuration

$config = [
    'sender' => [
        'name' => 'Clinic Name',
        'id' => 'org:identifier',
        'contact' => 'email@clinic.com',
    ],
    'requester' => [
        'name' => 'Doctor Name', 
        'id' => 'org:doctor.id',
        'contact' => 'doctor@clinic.com',
    ],
    'receivers' => [
        [
            'name' => 'Receiving Clinic',
            'id' => 'org:receiver.id', 
            'contact' => 'receiver@clinic.com',
        ],
    ],
    'patient' => [
        'name' => 'Patient Name',
        'dob' => '1975-02-14',
        'sex' => 'F',
        'ihi' => '8003608166690503', // Australian Individual Healthcare Identifier
    ],
];

Optional Configuration

$config = [
    // ... required fields above
    
    'custom_tags' => ['teaching', 'priority-review'],
    
    'security' => [
        'classification' => 'confidential', // or 'restricted', 'public'
    ],
    
    // Encryption (optional)
    'encryption' => [
        'recipient_public_key' => '<base64-encoded-public-key>', // Recipient's Curve25519 public key
    ],
    
    'report' => [
        'file' => 'files/report.pdf',
        'url' => 'https://example.com/report',
    ],
    
    'files' => [
        'file' => 'files/images.zip',
        'url' => 'https://example.com/images',
    ],
    
    'consent' => [
        'status' => 'granted',
        'scope' => ['treatment', 'research'],
        'method' => 'digital-signature',
    ],
    
    'deid_keys' => ['PatientName', 'PatientID', 'IssuerOfPatientID'],
    
    // Cryptographic assertions (for production use)
    'sender' => [
        // ... other fields
        'assertion' => [
            'alg' => 'Ed25519',
            'public_key' => '<base64_encoded_public_key>',
            'fingerprint' => 'SHA256:<hex_fingerprint>',
            'key_reference' => 'aurabox://org/clinic#key-ed25519',
            'signature' => '<base64_signature>',
            'expires_at' => '2025-12-31T23:59:59Z',
        ],
    ],
];

Output Structure

After processing, you'll have a JMIX envelope directory. The structure depends on whether encryption was used:

Unencrypted Envelope

<envelope-id>.JMIX/
├── manifest.json              # Security & routing metadata (includes payload_hash)
├── audit.json                 # Audit trail
└── payload/
    ├── metadata.json          # Medical data & patient info
    ├── dicom/                 # DICOM files (copied from source)
    │   ├── series_1/
    │   │   ├── CT.1.1.dcm
    │   │   └── ...
    │   └── series_2/
    │       └── ...
    ├── files/                 # Optional: report files and attachments
    │   └── report.pdf
    └── files.json             # File manifest (when files/ present)

Encrypted Envelope

<envelope-id>.JMIX/
├── manifest.json              # Security & routing metadata + encryption parameters
├── audit.json                 # Audit trail  
└── payload.encrypted          # AES-256-GCM encrypted TAR archive of payload/

The manifest.json in encrypted envelopes includes encryption details:

{
  "security": {
    "payload_hash": "sha256:abc123...",
    "encryption": {
      "algorithm": "AES-256-GCM",
      "ephemeral_public_key": "Y12JovXD3Hjc...",
      "iv": "uTv8nI6Wi/a/O7wc",
      "auth_tag": "5c8VZzxSuWM3RMqA..."
    }
  }
}

Each file is validated against its respective JSON schema before being saved.

Error Handling

The library provides specific exception types:

use AuraBox\Jmix\Exceptions\{JmixException, ValidationException, CryptographyException};

try {
    $envelope = $jmixBuilder->buildFromDicom($dicomPath, $config);
} catch (ValidationException $e) {
    echo "Validation failed: " . $e->getMessage() . "\n";
    foreach ($e->getErrors() as $error) {
        echo "  - $error\n"; 
    }
} catch (JmixException $e) {
    echo "JMIX error: " . $e->getMessage() . "\n";
}

Development

Requirements

  • PHP 8.1+
  • ext-json
  • ext-openssl
  • ext-sodium (for encryption/decryption)
  • Composer
  • Optional: DCMTK's dcmdump for enhanced DICOM metadata extraction

Setup

git clone https://github.com/aurabx/jmix-php
cd jmix-php/php-library
composer install

Testing

Samples are available under ./samples for demos and tests. Prefer writing to ./tmp per examples.

# Run all tests
composer test

# Run specific test files
vendor/bin/phpunit tests/JmixBuilderTest.php
vendor/bin/phpunit tests/JmixDecryptorTest.php
vendor/bin/phpunit tests/Encryption/PayloadEncryptorTest.php

# Run tests with coverage (requires Xdebug)
XDEBUG_MODE=coverage vendor/bin/phpunit

# Test CLI tools
bin/jmix-build ./samples/study_1 ./examples/sample-config.json ./tmp/test-output
bin/jmix-decrypt analyze ./tmp/test-output/*.JMIX

Code Quality

composer cs-check   # Check coding standards
composer cs-fix     # Fix coding standards
composer phpstan    # Static analysis
composer psalm      # Additional static analysis

Security Considerations

JWS Manifest Signing (optional)

You can sign manifest.json with a compact JWS (EdDSA/Ed25519) for integrity and authenticity.

  • Add jws_signing_key (base64 Ed25519 private key) to your build configuration
  • The builder will add a security.jws reference and emit a manifest.jws file when saving
  • Verify with JwsHandler::verifyJws(jws, publicKey)

Example (library):

use AuraBox\Jmix\JmixBuilder;

$config = [
    // ... your existing config
    'jws_signing_key' => '<base64-ed25519-private-key>',
];

$builder = new JmixBuilder();
$envelope = $builder->buildFromDicom('/path/to/dicom', $config);
$path = $builder->saveToFiles($envelope, './tmp/output', $config);
// Outputs manifest.json and manifest.jws

✅ Production-Ready Security Features

This library includes enterprise-grade encryption that is production-ready:

  • AES-256-GCM encryption with authenticated encryption
  • ECDH key exchange using Curve25519 elliptic curve
  • HKDF key derivation with SHA-256
  • Forward secrecy through ephemeral keypairs
  • Payload integrity verification with SHA-256 hashing
  • Memory safety with secure key clearing

⚠️ Additional Production Considerations

For production use, you should also consider:

  1. Key Management:

    • Use hardware security modules (HSMs) for key storage
    • Implement proper key rotation and expiration handling
    • Secure key distribution mechanisms
  2. Digital Signatures (currently placeholders):

    • Integrate with web-token/jwt-framework for JWT/JWS signatures
    • Use paragonie/constant_time_encoding for secure encoding
  3. Certificate Authority Integration:

    • Integrate with a certificate authority for directory attestations
    • Implement certificate validation and revocation checking
  4. Compliance:

    • Ensure compliance with healthcare data regulations (HIPAA, GDPR, etc.)
    • Implement audit logging for all cryptographic operations
    • Regular security assessments and penetration testing

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

See CHANGELOG.md for the full history, including 0.3.0, 0.2.0, and 0.1.0 entries.