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.
- blindvault_sdk-0.1.0rc1/LICENSE +21 -0
- blindvault_sdk-0.1.0rc1/MANIFEST.in +3 -0
- blindvault_sdk-0.1.0rc1/PKG-INFO +25 -0
- blindvault_sdk-0.1.0rc1/README.md +10 -0
- blindvault_sdk-0.1.0rc1/pyproject.toml +19 -0
- blindvault_sdk-0.1.0rc1/setup.cfg +4 -0
- blindvault_sdk-0.1.0rc1/src/blindvault_sdk/__init__.py +3 -0
- blindvault_sdk-0.1.0rc1/src/blindvault_sdk/crypto.py +63 -0
- blindvault_sdk-0.1.0rc1/src/blindvault_sdk/library.py +16 -0
- blindvault_sdk-0.1.0rc1/src/blindvault_sdk.egg-info/PKG-INFO +25 -0
- blindvault_sdk-0.1.0rc1/src/blindvault_sdk.egg-info/SOURCES.txt +12 -0
- blindvault_sdk-0.1.0rc1/src/blindvault_sdk.egg-info/dependency_links.txt +1 -0
- blindvault_sdk-0.1.0rc1/src/blindvault_sdk.egg-info/requires.txt +2 -0
- blindvault_sdk-0.1.0rc1/src/blindvault_sdk.egg-info/top_level.txt +1 -0
|
@@ -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,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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
blindvault_sdk
|