blindvault-sdk 0.1.0rc1__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BlindVault
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ prune generated
2
+ prune tests
3
+ global-exclude __pycache__ *.py[cod]
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: blindvault-sdk
3
+ Version: 0.1.0rc1
4
+ Summary: BlindVault local encryption and API SDK
5
+ Author-email: BlindVault <devops@blindvault.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://blindvault.com
8
+ Project-URL: Repository, https://gitlab.com/blindvault/blindvault-sdk
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: blindvault-sdk-api==0.1.0rc1
13
+ Requires-Dist: cryptography<49,>=46
14
+ Dynamic: license-file
15
+
16
+ # BlindVault SDK
17
+
18
+ Prerelease 0.1.0-rc.1. Local encryption, key wrapping and encrypted Library API operations.
19
+
20
+ This package requires a BlindVault account and backend access for network operations.
21
+ TEST/DEV require VPN access. Keys remain under application control.
22
+
23
+ Source and usage guide: https://gitlab.com/blindvault/blindvault-sdk
24
+
25
+ Contact: devops@blindvault.com
@@ -0,0 +1,10 @@
1
+ # BlindVault SDK
2
+
3
+ Prerelease 0.1.0-rc.1. Local encryption, key wrapping and encrypted Library API operations.
4
+
5
+ This package requires a BlindVault account and backend access for network operations.
6
+ TEST/DEV require VPN access. Keys remain under application control.
7
+
8
+ Source and usage guide: https://gitlab.com/blindvault/blindvault-sdk
9
+
10
+ Contact: devops@blindvault.com
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+ [project]
5
+ readme = "README.md"
6
+ description = "BlindVault local encryption and API SDK"
7
+ license-files = ["LICENSE"]
8
+ name = "blindvault-sdk"
9
+ version = "0.1.0rc1"
10
+ requires-python = ">=3.10"
11
+ dependencies = ["blindvault-sdk-api==0.1.0rc1", "cryptography>=46,<49"]
12
+ authors = [{name="BlindVault",email="devops@blindvault.com"}]
13
+ license = "MIT"
14
+ [tool.setuptools.packages.find]
15
+ where = ["src"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://blindvault.com"
19
+ Repository = "https://gitlab.com/blindvault/blindvault-sdk"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from blindvault_sdk_api import ApiClient, Configuration
2
+ from .crypto import *
3
+ from .library import LibraryClient
@@ -0,0 +1,63 @@
1
+ """BlindVault AES-GCM/RSA-OAEP wire format. Raw key exports must be protected by callers."""
2
+ import base64
3
+ import re
4
+ import secrets
5
+ from cryptography.hazmat.primitives import hashes, serialization
6
+ from cryptography.hazmat.primitives.asymmetric import rsa, padding
7
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
8
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
9
+ __all__ = ['to_base64','from_base64','generate_content_key','encrypt','decrypt','encrypt_text','decrypt_text','encrypt_file','decrypt_file','derive_passphrase_key','generate_identity','import_public_jwk','export_public_jwk','wrap_content_key','unwrap_content_key','protect_private_key','unlock_private_key','EncryptedContentClient']
10
+ def to_base64(value): return base64.b64encode(value).decode('ascii')
11
+ def from_base64(value):
12
+ if not isinstance(value,str) or not re.fullmatch(r'[A-Za-z0-9+/_-]*={0,2}',value): raise ValueError('Invalid base64')
13
+ normalized = value.replace('-','+').replace('_','/').rstrip('=')
14
+ if len(normalized)%4==1: raise ValueError('Invalid base64')
15
+ raw = base64.b64decode(normalized+'='*((-len(normalized))%4),validate=True)
16
+ if to_base64(raw).rstrip('=') != normalized: raise ValueError('Non-canonical base64')
17
+ return raw
18
+ def _size(value,n):
19
+ if len(value)!=n: raise ValueError(f'Expected {n} bytes')
20
+ return value
21
+ def generate_content_key(): return secrets.token_bytes(32)
22
+ def encrypt(data,key):
23
+ iv=secrets.token_bytes(12)
24
+ return {'ciphertext':to_base64(AESGCM(_size(key,32)).encrypt(iv,data,None)),'iv':to_base64(iv)}
25
+ def decrypt(value,key):
26
+ data=from_base64(value['ciphertext'])
27
+ if len(data)<16: raise ValueError('Ciphertext is missing its authentication tag')
28
+ return AESGCM(_size(key,32)).decrypt(_size(from_base64(value['iv']),12),data,None)
29
+ def encrypt_text(text,key): return encrypt(text.encode('utf-8'),key)
30
+ def decrypt_text(value,key): return decrypt(value,key).decode('utf-8')
31
+ def encrypt_file(data,filename,key): return {'content':encrypt(data,key),'filename':encrypt_text(filename,key)}
32
+ def decrypt_file(value,key): return {'data':decrypt(value['content'],key),'filename':decrypt_text(value['filename'],key)}
33
+ def derive_passphrase_key(passphrase,salt):
34
+ return PBKDF2HMAC(algorithm=hashes.SHA256(),length=32,salt=_size(salt,16),iterations=100000).derive(passphrase.encode('utf-8'))
35
+ def _rsa(key):
36
+ if not isinstance(key,(rsa.RSAPublicKey,rsa.RSAPrivateKey)) or key.key_size<2048: raise ValueError('RSA key must be at least 2048 bits')
37
+ return key
38
+ def _private(der): return _rsa(serialization.load_der_private_key(der,password=None))
39
+ def generate_identity():
40
+ key=rsa.generate_private_key(public_exponent=65537,key_size=2048)
41
+ return {'publicKeySpki':key.public_key().public_bytes(serialization.Encoding.DER,serialization.PublicFormat.SubjectPublicKeyInfo),'privateKeyPkcs8':key.private_bytes(serialization.Encoding.DER,serialization.PrivateFormat.PKCS8,serialization.NoEncryption())}
42
+ def import_public_jwk(jwk):
43
+ if jwk.get('kty')!='RSA': raise ValueError('RSA public JWK required')
44
+ key=_rsa(rsa.RSAPublicNumbers(int.from_bytes(from_base64(jwk['e']),'big'),int.from_bytes(from_base64(jwk['n']),'big')).public_key())
45
+ return key.public_bytes(serialization.Encoding.DER,serialization.PublicFormat.SubjectPublicKeyInfo)
46
+ def export_public_jwk(spki):
47
+ numbers=_rsa(serialization.load_der_public_key(spki)).public_numbers()
48
+ encode=lambda n: to_base64(n.to_bytes((n.bit_length()+7)//8,'big')).rstrip('=').replace('+','-').replace('/','_')
49
+ return {'kty':'RSA','n':encode(numbers.n),'e':encode(numbers.e),'alg':'RSA-OAEP-256','ext':True,'key_ops':['encrypt']}
50
+ def _oaep(): return padding.OAEP(mgf=padding.MGF1(hashes.SHA256()),algorithm=hashes.SHA256(),label=None)
51
+ def wrap_content_key(key,spki): return to_base64(_rsa(serialization.load_der_public_key(spki)).encrypt(_size(key,32),_oaep()))
52
+ def unwrap_content_key(wrapped,pkcs8): return _size(_private(pkcs8).decrypt(from_base64(wrapped),_oaep()),32)
53
+ def protect_private_key(pkcs8,passphrase):
54
+ _private(pkcs8)
55
+ salt=secrets.token_bytes(16); envelope=encrypt(pkcs8,derive_passphrase_key(passphrase,salt))
56
+ return {'encryptedPrivateKey':envelope['ciphertext'],'salt':to_base64(salt),'iv':envelope['iv']}
57
+ def unlock_private_key(value,passphrase):
58
+ der=decrypt({'ciphertext':value['encryptedPrivateKey'],'iv':value['iv']},derive_passphrase_key(passphrase,from_base64(value['salt'])))
59
+ _private(der)
60
+ return der
61
+ class EncryptedContentClient:
62
+ def encrypt_and_upload(self,data,filename,key,upload): return upload(encrypt_file(data,filename,key))
63
+ def download_and_decrypt(self,key,download): return decrypt_file(download(),key)
@@ -0,0 +1,16 @@
1
+ """Library API adapter using the agent's versioned filename envelope."""
2
+ import json
3
+ from .crypto import encrypt_file, decrypt_file, from_base64
4
+ class LibraryClient:
5
+ def __init__(self,library_api,encrypted_file_api):
6
+ self.library=library_api; self.files=encrypted_file_api
7
+ def encrypt_and_upload(self,collection_id,data,filename,key,content_type='application/octet-stream'):
8
+ value=encrypt_file(data,filename,key)
9
+ return self.library.upload_file(collection_id=collection_id,file=('encrypted-file.bin',from_base64(value['content']['ciphertext'])),encrypted_filename=json.dumps({'version':1,**value['filename']},separators=(',',':')),iv=value['content']['iv'],content_type=content_type,filename='encrypted-file.bin')
10
+ def download_and_decrypt(self,file_id,iv,encrypted_filename,key):
11
+ if encrypted_filename.startswith('{'):
12
+ filename=json.loads(encrypted_filename)
13
+ if filename.get('version')!=1 or not isinstance(filename.get('iv'),str) or not isinstance(filename.get('ciphertext'),str): raise ValueError('Unsupported filename envelope')
14
+ else: filename={'iv':iv,'ciphertext':encrypted_filename}
15
+ ciphertext=self.files.download_encrypted_file_by_query(file_id)
16
+ return decrypt_file({'content':{'iv':iv,'ciphertext':ciphertext},'filename':filename},key)
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: blindvault-sdk
3
+ Version: 0.1.0rc1
4
+ Summary: BlindVault local encryption and API SDK
5
+ Author-email: BlindVault <devops@blindvault.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://blindvault.com
8
+ Project-URL: Repository, https://gitlab.com/blindvault/blindvault-sdk
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: blindvault-sdk-api==0.1.0rc1
13
+ Requires-Dist: cryptography<49,>=46
14
+ Dynamic: license-file
15
+
16
+ # BlindVault SDK
17
+
18
+ Prerelease 0.1.0-rc.1. Local encryption, key wrapping and encrypted Library API operations.
19
+
20
+ This package requires a BlindVault account and backend access for network operations.
21
+ TEST/DEV require VPN access. Keys remain under application control.
22
+
23
+ Source and usage guide: https://gitlab.com/blindvault/blindvault-sdk
24
+
25
+ Contact: devops@blindvault.com
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ src/blindvault_sdk/__init__.py
6
+ src/blindvault_sdk/crypto.py
7
+ src/blindvault_sdk/library.py
8
+ src/blindvault_sdk.egg-info/PKG-INFO
9
+ src/blindvault_sdk.egg-info/SOURCES.txt
10
+ src/blindvault_sdk.egg-info/dependency_links.txt
11
+ src/blindvault_sdk.egg-info/requires.txt
12
+ src/blindvault_sdk.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ blindvault-sdk-api==0.1.0rc1
2
+ cryptography<49,>=46