inferlock 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: inferlock
3
+ Version: 0.4.0
4
+ Summary: File encryption and licensing for AI models
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: cryptography
8
+
9
+ # inferlock
10
+
11
+ Protect AI model files with encryption and license-based runtime loading.
12
+
13
+ inferlock encrypts model files and loads them securely with expiration-based licenses.
14
+
15
+ ---
16
+
17
+ ## Features
18
+
19
+ * Model file encryption
20
+ * Runtime in-memory decryption
21
+ * License-based loading
22
+ * Expiration-based license
23
+ * Single-command packaging
24
+ * Bundle-based distribution
25
+ * CLI-first design
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install inferlock
33
+ ```
34
+
35
+ or local dev:
36
+
37
+ ```bash
38
+ pip install -e .
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Quick Start
44
+
45
+ Encrypt model and create license:
46
+
47
+ ```bash
48
+ inferlock encrypt model.pt --expire 30d
49
+ ```
50
+
51
+ Output:
52
+
53
+ ```
54
+ model.inferlock/
55
+ ├── model.inferlock
56
+ └── license.inferlock
57
+ ```
58
+
59
+ Load model:
60
+
61
+ ```python
62
+ from inferlock import load
63
+
64
+ model = load("model.inferlock")
65
+ ```
66
+
67
+ ---
68
+
69
+ ## CLI Usage
70
+
71
+ Encrypt model:
72
+
73
+ ```bash
74
+ inferlock encrypt model.pt --expire 30d
75
+ ```
76
+
77
+ Generate license manually:
78
+
79
+ ```bash
80
+ inferlock license model.key --expire 30d
81
+ ```
82
+
83
+ Show bundle info:
84
+
85
+ ```bash
86
+ inferlock info model.inferlock
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Device Binding
92
+
93
+ Bind license to machine:
94
+
95
+ ```bash
96
+ inferlock encrypt model.pt --expire 30d --bind-device
97
+ ```
98
+
99
+ The license will be tied to the current machine's MAC address and cannot be used on other devices.
100
+
101
+ ---
102
+
103
+ ## Bundle Structure
104
+
105
+ ```
106
+ model.inferlock/
107
+ ├── model.inferlock
108
+ └── license.inferlock
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Python API
114
+
115
+ Load bundle:
116
+
117
+ ```python
118
+ from inferlock import load
119
+
120
+ model = load("model.inferlock")
121
+ ```
122
+
123
+ Load manually:
124
+
125
+ ```python
126
+ load("model.inferlock", "license.inferlock")
127
+ ```
128
+
129
+ ---
130
+
131
+ ## License Format
132
+
133
+ ```json
134
+ {
135
+ "key": "...",
136
+ "expire": "YYYY-MM-DD",
137
+ "version": 1
138
+ }
139
+ ```
140
+
141
+ ## Version
142
+
143
+ ### v0.2.0
144
+
145
+ * license system
146
+ * expiration support
147
+ * bundle packaging
148
+ * single command encrypt + license
149
+ * auto bundle loader
150
+
151
+ ### v0.3.0
152
+
153
+ * device binding (MAC)
154
+ * per-device license
155
+
156
+ ### v0.4.0
157
+
158
+ * signed license
159
+ * tamper protection
160
+
161
+ ### v0.5.0
162
+
163
+ * encrypted license
164
+ * hidden key
165
+
166
+ ---
167
+
168
+ ## Roadmap
169
+
170
+ Future features:
171
+
172
+ * multi-device license
173
+ * trial license
174
+ * offline activation
175
+ * hardware fingerprint
176
+ * bulk license generation
177
+ * license revoke
178
+ * metadata bundle
179
+ * verify command
180
+
181
+ ---
182
+
183
+ ## Example
184
+
185
+ ```bash
186
+ inferlock encrypt yolov7.weights --expire 7d
187
+ ```
188
+
189
+ Load:
190
+
191
+ ```python
192
+ load("yolov7.inferlock")
193
+ ```
@@ -0,0 +1,185 @@
1
+ # inferlock
2
+
3
+ Protect AI model files with encryption and license-based runtime loading.
4
+
5
+ inferlock encrypts model files and loads them securely with expiration-based licenses.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ * Model file encryption
12
+ * Runtime in-memory decryption
13
+ * License-based loading
14
+ * Expiration-based license
15
+ * Single-command packaging
16
+ * Bundle-based distribution
17
+ * CLI-first design
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install inferlock
25
+ ```
26
+
27
+ or local dev:
28
+
29
+ ```bash
30
+ pip install -e .
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Quick Start
36
+
37
+ Encrypt model and create license:
38
+
39
+ ```bash
40
+ inferlock encrypt model.pt --expire 30d
41
+ ```
42
+
43
+ Output:
44
+
45
+ ```
46
+ model.inferlock/
47
+ ├── model.inferlock
48
+ └── license.inferlock
49
+ ```
50
+
51
+ Load model:
52
+
53
+ ```python
54
+ from inferlock import load
55
+
56
+ model = load("model.inferlock")
57
+ ```
58
+
59
+ ---
60
+
61
+ ## CLI Usage
62
+
63
+ Encrypt model:
64
+
65
+ ```bash
66
+ inferlock encrypt model.pt --expire 30d
67
+ ```
68
+
69
+ Generate license manually:
70
+
71
+ ```bash
72
+ inferlock license model.key --expire 30d
73
+ ```
74
+
75
+ Show bundle info:
76
+
77
+ ```bash
78
+ inferlock info model.inferlock
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Device Binding
84
+
85
+ Bind license to machine:
86
+
87
+ ```bash
88
+ inferlock encrypt model.pt --expire 30d --bind-device
89
+ ```
90
+
91
+ The license will be tied to the current machine's MAC address and cannot be used on other devices.
92
+
93
+ ---
94
+
95
+ ## Bundle Structure
96
+
97
+ ```
98
+ model.inferlock/
99
+ ├── model.inferlock
100
+ └── license.inferlock
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Python API
106
+
107
+ Load bundle:
108
+
109
+ ```python
110
+ from inferlock import load
111
+
112
+ model = load("model.inferlock")
113
+ ```
114
+
115
+ Load manually:
116
+
117
+ ```python
118
+ load("model.inferlock", "license.inferlock")
119
+ ```
120
+
121
+ ---
122
+
123
+ ## License Format
124
+
125
+ ```json
126
+ {
127
+ "key": "...",
128
+ "expire": "YYYY-MM-DD",
129
+ "version": 1
130
+ }
131
+ ```
132
+
133
+ ## Version
134
+
135
+ ### v0.2.0
136
+
137
+ * license system
138
+ * expiration support
139
+ * bundle packaging
140
+ * single command encrypt + license
141
+ * auto bundle loader
142
+
143
+ ### v0.3.0
144
+
145
+ * device binding (MAC)
146
+ * per-device license
147
+
148
+ ### v0.4.0
149
+
150
+ * signed license
151
+ * tamper protection
152
+
153
+ ### v0.5.0
154
+
155
+ * encrypted license
156
+ * hidden key
157
+
158
+ ---
159
+
160
+ ## Roadmap
161
+
162
+ Future features:
163
+
164
+ * multi-device license
165
+ * trial license
166
+ * offline activation
167
+ * hardware fingerprint
168
+ * bulk license generation
169
+ * license revoke
170
+ * metadata bundle
171
+ * verify command
172
+
173
+ ---
174
+
175
+ ## Example
176
+
177
+ ```bash
178
+ inferlock encrypt yolov7.weights --expire 7d
179
+ ```
180
+
181
+ Load:
182
+
183
+ ```python
184
+ load("yolov7.inferlock")
185
+ ```
@@ -0,0 +1,7 @@
1
+ """InferLock - File encryption and licensing for AI models."""
2
+
3
+ from .crypto import encrypt_bytes, decrypt_bytes
4
+ from .format import pack_file, unpack_file
5
+ from .loader import load_model as load
6
+
7
+ __version__ = "0.1.0"
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import base64
4
+ import datetime
5
+ import json
6
+ import os
7
+
8
+ from .crypto import encrypt_bytes
9
+ from .format import pack_file, unpack_file
10
+ from .license import create_license as create_license_bytes, parse_license, get_device_id
11
+
12
+
13
+ def parse_expire(expire: str) -> datetime.datetime:
14
+ """Parse expiration string to datetime."""
15
+ if expire.endswith('d'):
16
+ days = int(expire[:-1])
17
+ return datetime.datetime.now() + datetime.timedelta(days=days)
18
+ try:
19
+ return datetime.datetime.strptime(expire, "%Y-%m-%d")
20
+ except ValueError:
21
+ raise argparse.ArgumentTypeError(f"Invalid expire format: {expire}")
22
+
23
+
24
+ def cmd_encrypt(args):
25
+ with open(args.input, 'rb') as f:
26
+ data = f.read()
27
+
28
+ # Generate random 32-byte key and base64 encode
29
+ key_bytes = os.urandom(32)
30
+ key_b64 = base64.b64encode(key_bytes).decode('utf-8')
31
+
32
+ # Encrypt file using the generated key
33
+ encrypted = encrypt_bytes(data, key_b64)
34
+
35
+ header = {
36
+ "format": "inferlock-v1",
37
+ "original_name": args.input,
38
+ }
39
+ packed = pack_file(header, encrypted)
40
+
41
+ # Determine bundle directory name
42
+ if args.output:
43
+ bundle_dir = args.output
44
+ else:
45
+ base_name = os.path.splitext(args.input)[0]
46
+ bundle_dir = f"{base_name}.inferlock"
47
+
48
+ # Ensure bundle_dir ends with .inferlock
49
+ if not bundle_dir.endswith(".inferlock"):
50
+ bundle_dir = f"{bundle_dir}.inferlock"
51
+
52
+ os.makedirs(bundle_dir, exist_ok=True)
53
+
54
+ # Save encrypted model inside as "model.inferlock"
55
+ model_path = os.path.join(bundle_dir, "model.inferlock")
56
+ with open(model_path, 'wb') as f:
57
+ f.write(packed)
58
+
59
+ # Save license inside as "license.inferlock" (if expire provided)
60
+ if args.expire:
61
+ device = get_device_id() if args.bind_device else None
62
+ license_bytes = create_license_bytes(key_b64, args.expire, device)
63
+ license_path = os.path.join(bundle_dir, args.license_name)
64
+ with open(license_path, 'wb') as f:
65
+ f.write(license_bytes)
66
+ print(f"Encrypted: {args.input} -> {bundle_dir}/")
67
+ print(f"Files: model.inferlock, {args.license_name}")
68
+ else:
69
+ print(f"Encrypted: {args.input} -> {bundle_dir}/model.inferlock")
70
+
71
+
72
+ def cmd_license(args):
73
+ with open(args.keyfile, 'r') as f:
74
+ key = f.read().strip()
75
+
76
+ device = get_device_id() if args.bind_device else None
77
+ license_bytes = create_license_bytes(key, args.expire, device)
78
+
79
+ output = args.output if args.output else "license.inferlock"
80
+ with open(output, 'wb') as f:
81
+ f.write(license_bytes)
82
+
83
+ print(f"License created: {output}")
84
+
85
+
86
+ def print_bundle_info(bundle_path):
87
+ """Print bundle information."""
88
+ print(f"Model bundle: {bundle_path}")
89
+
90
+ # Check if license.inferlock exists in bundle
91
+ license_path = os.path.join(bundle_path, "license.inferlock")
92
+ if os.path.exists(license_path):
93
+ with open(license_path, 'rb') as f:
94
+ license_data = f.read()
95
+
96
+ license_dict = parse_license(license_data)
97
+
98
+ print(f"Expire: {license_dict.get('expire', 'unknown')}")
99
+ print(f"Version: {license_dict.get('version', 'unknown')}")
100
+
101
+ if "device" in license_dict:
102
+ print("Device bound: yes")
103
+ else:
104
+ print("Device bound: no")
105
+ else:
106
+ print("No license found")
107
+
108
+
109
+ def cmd_info(args):
110
+ print_bundle_info(args.input)
111
+
112
+
113
+ def main():
114
+ parser = argparse.ArgumentParser(description="InferLock CLI")
115
+ subparsers = parser.add_subparsers(dest='command', required=True)
116
+
117
+ encrypt_parser = subparsers.add_parser('encrypt', help='Encrypt a file')
118
+ encrypt_parser.add_argument('input', help='Input file')
119
+ encrypt_parser.add_argument('--expire', help='Expiration (30d, 7d, 2026-12-01)')
120
+ encrypt_parser.add_argument('-o', '--output', help='Output file')
121
+ encrypt_parser.add_argument('--password', help='Password')
122
+ encrypt_parser.add_argument('--bind-device', action='store_true', help='Bind license to device')
123
+ encrypt_parser.add_argument('--license-name', default='license.inferlock', help='License filename inside bundle')
124
+ encrypt_parser.set_defaults(func=cmd_encrypt)
125
+
126
+ license_parser = subparsers.add_parser('license', help='Create license')
127
+ license_parser.add_argument('keyfile', help='Key file (.key)')
128
+ license_parser.add_argument('--expire', required=True, help='Expiration (30d, 7d, 2026-12-01)')
129
+ license_parser.add_argument('-o', '--output', help='Output file')
130
+ license_parser.add_argument('--bind-device', action='store_true', help='Bind license to device')
131
+ license_parser.set_defaults(func=cmd_license)
132
+
133
+ info_parser = subparsers.add_parser('info', help='Show file info')
134
+ info_parser.add_argument('input', help='Input .inferlock file')
135
+ info_parser.set_defaults(func=cmd_info)
136
+
137
+ args = parser.parse_args()
138
+ args.func(args)
139
+
140
+
141
+ if __name__ == "__main__":
142
+ main()
@@ -0,0 +1,61 @@
1
+ import os
2
+ from typing import Tuple
3
+
4
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
5
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
6
+ from cryptography.hazmat.primitives import hashes
7
+ from cryptography.hazmat.backends import default_backend
8
+
9
+
10
+ # Constants
11
+ KEY_SIZE = 32 # 256 bits
12
+ SALT_SIZE = 16
13
+ NONCE_SIZE = 12 # 96 bits for GCM
14
+ ITERATIONS = 600_000
15
+
16
+
17
+ def _derive_key(password: str, salt: bytes) -> bytes:
18
+ """Derive a 256-bit key from password using PBKDF2-HMAC-SHA256."""
19
+ kdf = PBKDF2HMAC(
20
+ algorithm=hashes.SHA256(),
21
+ length=KEY_SIZE,
22
+ salt=salt,
23
+ iterations=ITERATIONS,
24
+ backend=default_backend(),
25
+ )
26
+ return kdf.derive(password.encode('utf-8'))
27
+
28
+
29
+ def encrypt_bytes(data: bytes, password: str) -> bytes:
30
+ """
31
+ Encrypt bytes using AES-256-GCM with password-derived key.
32
+
33
+ Returns salt + nonce + ciphertext + tag (16 bytes).
34
+ """
35
+ salt = os.urandom(SALT_SIZE)
36
+ nonce = os.urandom(NONCE_SIZE)
37
+ key = _derive_key(password, salt)
38
+
39
+ aesgcm = AESGCM(key)
40
+ ciphertext = aesgcm.encrypt(nonce, data, None)
41
+
42
+ return salt + nonce + ciphertext
43
+
44
+
45
+ def decrypt_bytes(data: bytes, password: str) -> bytes:
46
+ """
47
+ Decrypt bytes using AES-256-GCM with password-derived key.
48
+
49
+ Expects salt + nonce + ciphertext + tag (16 bytes).
50
+ """
51
+ if len(data) < SALT_SIZE + NONCE_SIZE + 16:
52
+ raise ValueError("Data too short to contain valid encrypted content")
53
+
54
+ salt = data[:SALT_SIZE]
55
+ nonce = data[SALT_SIZE:SALT_SIZE + NONCE_SIZE]
56
+ ciphertext = data[SALT_SIZE + NONCE_SIZE:]
57
+
58
+ key = _derive_key(password, salt)
59
+
60
+ aesgcm = AESGCM(key)
61
+ return aesgcm.decrypt(nonce, ciphertext, None)
@@ -0,0 +1,31 @@
1
+ import json
2
+ from typing import Tuple
3
+
4
+ DELIMITER = b"INFERLOCK"
5
+
6
+
7
+ def pack_file(header: dict, data: bytes) -> bytes:
8
+ """
9
+ Pack a file with JSON header + delimiter + data.
10
+
11
+ Format: header_json + b"INFERLOCK" + data
12
+ """
13
+ header_bytes = json.dumps(header).encode('utf-8')
14
+ return header_bytes + DELIMITER + data
15
+
16
+
17
+ def unpack_file(file_bytes: bytes) -> Tuple[dict, bytes]:
18
+ """
19
+ Unpack a file into header dict and data bytes.
20
+
21
+ Expects: header_json + b"INFERLOCK" + data
22
+ """
23
+ delimiter_pos = file_bytes.find(DELIMITER)
24
+ if delimiter_pos == -1:
25
+ raise ValueError("Missing delimiter in file data")
26
+
27
+ header_bytes = file_bytes[:delimiter_pos]
28
+ header = json.loads(header_bytes.decode('utf-8'))
29
+ encrypted_bytes = file_bytes[delimiter_pos + len(DELIMITER):]
30
+
31
+ return header, encrypted_bytes
@@ -0,0 +1,199 @@
1
+ import base64
2
+ import hashlib
3
+ import json
4
+ import uuid
5
+ from datetime import datetime, timedelta
6
+ from cryptography.hazmat.primitives.asymmetric import ed25519
7
+ from cryptography.hazmat.primitives import serialization
8
+
9
+ _PK = "..." # Base64 encoded Ed25519 public key
10
+
11
+
12
+ def _parse_expire(expire: str) -> datetime:
13
+ """Parse expiration string to datetime."""
14
+ if expire.endswith('d'):
15
+ days = int(expire[:-1])
16
+ return datetime.now() + timedelta(days=days)
17
+ try:
18
+ return datetime.strptime(expire, "%Y-%m-%d")
19
+ except ValueError:
20
+ raise ValueError(f"Invalid expire format: {expire}")
21
+
22
+
23
+ def create_license(key: str, expire: str, device: str = None, private_key: str = None) -> bytes:
24
+ """
25
+ Create license bytes.
26
+
27
+ Args:
28
+ key: License key string
29
+ expire: Expiration string (30d, 7d, YYYY-MM-DD)
30
+ device: Optional device ID to bind license
31
+ private_key: Base64 encoded private key for signing (optional)
32
+
33
+ Returns:
34
+ JSON license bytes with optional signature
35
+ """
36
+ expire_dt = _parse_expire(expire)
37
+
38
+ license_data = {
39
+ "key": key,
40
+ "expire": expire_dt.strftime("%Y-%m-%d"),
41
+ "version": 1,
42
+ }
43
+
44
+ if device:
45
+ license_data["device"] = device
46
+
47
+ if private_key:
48
+ license_json = json.dumps(license_data).encode('utf-8')
49
+ signature = sign_data(private_key, license_json)
50
+ return json.dumps({
51
+ "data": license_data,
52
+ "signature": signature
53
+ }).encode('utf-8')
54
+
55
+ return json.dumps(license_data).encode('utf-8')
56
+
57
+
58
+ def parse_license(data: bytes) -> dict:
59
+ """
60
+ Parse license bytes to dict.
61
+
62
+ Args:
63
+ data: License bytes
64
+
65
+ Returns:
66
+ License dictionary (handles both old and new formats)
67
+ """
68
+ parsed = json.loads(data.decode('utf-8'))
69
+
70
+ # New format with signature
71
+ if "data" in parsed:
72
+ return parsed["data"]
73
+
74
+ # Old format (backward compatible)
75
+ return parsed
76
+
77
+
78
+ def _v(data: bytes) -> dict:
79
+ """
80
+ Verify license signature and return license data.
81
+
82
+ Args:
83
+ data: License bytes
84
+
85
+ Returns:
86
+ License dictionary
87
+
88
+ Raises:
89
+ Exception: Invalid license signature
90
+ """
91
+ parsed = json.loads(data.decode('utf-8'))
92
+
93
+ # New format with signature
94
+ if "signature" in parsed:
95
+ license_data = "data" in parsed and parsed["data"] or parsed
96
+ license_json = json.dumps(license_data).encode('utf-8')
97
+
98
+ if not verify_signature(_PK, license_json, parsed["signature"]):
99
+ raise Exception("Invalid license signature")
100
+
101
+ return license_data
102
+
103
+ # Old format (backward compatible)
104
+ return parsed
105
+
106
+
107
+ def is_expired(license_dict: dict) -> bool:
108
+ """
109
+ Check if license is expired.
110
+
111
+ Args:
112
+ license_dict: License dictionary
113
+
114
+ Returns:
115
+ True if expired, False otherwise
116
+ """
117
+ expire_at = datetime.strptime(license_dict["expire"], "%Y-%m-%d")
118
+ return datetime.now() >= expire_at
119
+
120
+
121
+ def get_device_id() -> str:
122
+ """
123
+ Get unique device identifier.
124
+
125
+ Returns:
126
+ Hex string of hashed MAC address
127
+ """
128
+ mac = uuid.getnode()
129
+ hashed = hashlib.sha256(str(mac).encode()).hexdigest()
130
+ return hashed
131
+
132
+
133
+ def generate_keypair() -> tuple[str, str]:
134
+ """
135
+ Generate Ed25519 keypair.
136
+
137
+ Returns:
138
+ Tuple of (private_key_b64, public_key_b64) as base64 strings
139
+ """
140
+ private_key = ed25519.Ed25519PrivateKey.generate()
141
+ public_key = private_key.public_key()
142
+
143
+ private_bytes = private_key.private_bytes(
144
+ encoding=serialization.Encoding.Raw,
145
+ format=serialization.PrivateFormat.Raw,
146
+ encryption_algorithm=serialization.NoEncryption()
147
+ )
148
+
149
+ public_bytes = public_key.public_bytes(
150
+ encoding=serialization.Encoding.Raw,
151
+ format=serialization.PublicFormat.Raw
152
+ )
153
+
154
+ return (
155
+ base64.b64encode(private_bytes).decode('utf-8'),
156
+ base64.b64encode(public_bytes).decode('utf-8')
157
+ )
158
+
159
+
160
+ def sign_data(private_key: str, data_bytes: bytes) -> str:
161
+ """
162
+ Sign data bytes with Ed25519 private key.
163
+
164
+ Args:
165
+ private_key: Base64 encoded private key
166
+ data_bytes: Data to sign
167
+
168
+ Returns:
169
+ Base64 encoded signature
170
+ """
171
+ private_bytes = base64.b64decode(private_key)
172
+ private_key_obj = ed25519.Ed25519PrivateKey.from_private_bytes(private_bytes)
173
+
174
+ signature = private_key_obj.sign(data_bytes)
175
+ return base64.b64encode(signature).decode('utf-8')
176
+
177
+
178
+ def verify_signature(public_key: str, data_bytes: bytes, signature: str) -> bool:
179
+ """
180
+ Verify signature with Ed25519 public key.
181
+
182
+ Args:
183
+ public_key: Base64 encoded public key
184
+ data_bytes: Original data
185
+ signature: Base64 encoded signature
186
+
187
+ Returns:
188
+ True if signature is valid, False otherwise
189
+ """
190
+ public_bytes = base64.b64decode(public_key)
191
+ public_key_obj = ed25519.Ed25519PublicKey.from_public_bytes(public_bytes)
192
+
193
+ signature_bytes = base64.b64decode(signature)
194
+
195
+ try:
196
+ public_key_obj.verify(signature_bytes, data_bytes)
197
+ return True
198
+ except Exception:
199
+ return False
@@ -0,0 +1,67 @@
1
+ import io
2
+ import os
3
+
4
+ from .crypto import decrypt_bytes
5
+ from .format import unpack_file
6
+ from .license import _v, is_expired, get_device_id
7
+
8
+
9
+ def load_model(path: str, license: str = None):
10
+ """
11
+ Load a model from an encrypted .inferlock file or bundle directory.
12
+
13
+ Args:
14
+ path: Path to .inferlock file or bundle directory
15
+ license: Path to license.inferlock file (required if path is file)
16
+
17
+ Returns:
18
+ Loaded model object
19
+ """
20
+ # Handle bundle directory
21
+ if os.path.isdir(path):
22
+ model_path = os.path.join(path, "model.inferlock")
23
+ license_path = os.path.join(path, "license.inferlock")
24
+ else:
25
+ # Handle file path
26
+ if not license:
27
+ raise Exception("License required")
28
+ model_path = path
29
+ license_path = license
30
+
31
+ # Read and parse license
32
+ with open(license_path, 'rb') as f:
33
+ license_data = f.read()
34
+
35
+ try:
36
+ license_dict = _v(license_data)
37
+ except Exception:
38
+ raise Exception("License validation failed")
39
+
40
+ # Check expiration
41
+ if is_expired(license_dict):
42
+ raise Exception("License validation failed")
43
+
44
+ # Check device binding
45
+ if "device" in license_dict:
46
+ current_device = get_device_id()
47
+ if current_device != license_dict["device"]:
48
+ raise Exception("License validation failed")
49
+
50
+ # Extract key from license
51
+ key = license_dict["key"]
52
+
53
+ # Read and decrypt model
54
+ with open(model_path, 'rb') as f:
55
+ file_bytes = f.read()
56
+
57
+ header, encrypted = unpack_file(file_bytes)
58
+ decrypted = decrypt_bytes(encrypted, key)
59
+
60
+ original_name = header.get("original_name", "")
61
+
62
+ if original_name.endswith(".pt"):
63
+ import torch
64
+ return torch.load(io.BytesIO(decrypted))
65
+
66
+ # Default: return bytes for other formats
67
+ return decrypted
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: inferlock
3
+ Version: 0.4.0
4
+ Summary: File encryption and licensing for AI models
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: cryptography
8
+
9
+ # inferlock
10
+
11
+ Protect AI model files with encryption and license-based runtime loading.
12
+
13
+ inferlock encrypts model files and loads them securely with expiration-based licenses.
14
+
15
+ ---
16
+
17
+ ## Features
18
+
19
+ * Model file encryption
20
+ * Runtime in-memory decryption
21
+ * License-based loading
22
+ * Expiration-based license
23
+ * Single-command packaging
24
+ * Bundle-based distribution
25
+ * CLI-first design
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install inferlock
33
+ ```
34
+
35
+ or local dev:
36
+
37
+ ```bash
38
+ pip install -e .
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Quick Start
44
+
45
+ Encrypt model and create license:
46
+
47
+ ```bash
48
+ inferlock encrypt model.pt --expire 30d
49
+ ```
50
+
51
+ Output:
52
+
53
+ ```
54
+ model.inferlock/
55
+ ├── model.inferlock
56
+ └── license.inferlock
57
+ ```
58
+
59
+ Load model:
60
+
61
+ ```python
62
+ from inferlock import load
63
+
64
+ model = load("model.inferlock")
65
+ ```
66
+
67
+ ---
68
+
69
+ ## CLI Usage
70
+
71
+ Encrypt model:
72
+
73
+ ```bash
74
+ inferlock encrypt model.pt --expire 30d
75
+ ```
76
+
77
+ Generate license manually:
78
+
79
+ ```bash
80
+ inferlock license model.key --expire 30d
81
+ ```
82
+
83
+ Show bundle info:
84
+
85
+ ```bash
86
+ inferlock info model.inferlock
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Device Binding
92
+
93
+ Bind license to machine:
94
+
95
+ ```bash
96
+ inferlock encrypt model.pt --expire 30d --bind-device
97
+ ```
98
+
99
+ The license will be tied to the current machine's MAC address and cannot be used on other devices.
100
+
101
+ ---
102
+
103
+ ## Bundle Structure
104
+
105
+ ```
106
+ model.inferlock/
107
+ ├── model.inferlock
108
+ └── license.inferlock
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Python API
114
+
115
+ Load bundle:
116
+
117
+ ```python
118
+ from inferlock import load
119
+
120
+ model = load("model.inferlock")
121
+ ```
122
+
123
+ Load manually:
124
+
125
+ ```python
126
+ load("model.inferlock", "license.inferlock")
127
+ ```
128
+
129
+ ---
130
+
131
+ ## License Format
132
+
133
+ ```json
134
+ {
135
+ "key": "...",
136
+ "expire": "YYYY-MM-DD",
137
+ "version": 1
138
+ }
139
+ ```
140
+
141
+ ## Version
142
+
143
+ ### v0.2.0
144
+
145
+ * license system
146
+ * expiration support
147
+ * bundle packaging
148
+ * single command encrypt + license
149
+ * auto bundle loader
150
+
151
+ ### v0.3.0
152
+
153
+ * device binding (MAC)
154
+ * per-device license
155
+
156
+ ### v0.4.0
157
+
158
+ * signed license
159
+ * tamper protection
160
+
161
+ ### v0.5.0
162
+
163
+ * encrypted license
164
+ * hidden key
165
+
166
+ ---
167
+
168
+ ## Roadmap
169
+
170
+ Future features:
171
+
172
+ * multi-device license
173
+ * trial license
174
+ * offline activation
175
+ * hardware fingerprint
176
+ * bulk license generation
177
+ * license revoke
178
+ * metadata bundle
179
+ * verify command
180
+
181
+ ---
182
+
183
+ ## Example
184
+
185
+ ```bash
186
+ inferlock encrypt yolov7.weights --expire 7d
187
+ ```
188
+
189
+ Load:
190
+
191
+ ```python
192
+ load("yolov7.inferlock")
193
+ ```
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ inferlock/__init__.py
4
+ inferlock/cli.py
5
+ inferlock/crypto.py
6
+ inferlock/format.py
7
+ inferlock/license.py
8
+ inferlock/loader.py
9
+ inferlock.egg-info/PKG-INFO
10
+ inferlock.egg-info/SOURCES.txt
11
+ inferlock.egg-info/dependency_links.txt
12
+ inferlock.egg-info/entry_points.txt
13
+ inferlock.egg-info/requires.txt
14
+ inferlock.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ inferlock = inferlock.cli:main
@@ -0,0 +1 @@
1
+ cryptography
@@ -0,0 +1,4 @@
1
+ dist
2
+ inferlock
3
+ log-model
4
+ venv
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "inferlock"
7
+ version = "0.4.0"
8
+ description = "File encryption and licensing for AI models"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = [
12
+ "cryptography",
13
+ ]
14
+
15
+ [project.scripts]
16
+ inferlock = "inferlock.cli:main"
17
+
18
+ [tool.setuptools.packages.find]
19
+ where = ["."]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+