├── .github └── workflows │ ├── archive.yml │ ├── ghpages.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── .note.xml ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── draft-irtf-cfrg-opaque.md └── poc ├── .gitignore ├── Makefile ├── ake_group.sage ├── format_test_vectors.py ├── hash.py ├── opaque_ake.sage ├── opaque_common.sage ├── opaque_core.sage ├── opaque_drng.sage ├── opaque_messages.sage ├── rfc7748.sage ├── string_utils.sage ├── test_drng.sage ├── test_opaque_ake.sage └── vectors ├── formatted.txt └── vectors.json /.github/workflows/archive.yml: -------------------------------------------------------------------------------- 1 | name: "Archive Issues and Pull Requests" 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * 0,2,4' 6 | repository_dispatch: 7 | types: [archive] 8 | 9 | jobs: 10 | build: 11 | name: "Archive Issues and Pull Requests" 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: "Checkout" 15 | uses: actions/checkout@v2 16 | 17 | - name: "Update Archive" 18 | uses: martinthomson/i-d-template@v1 19 | with: 20 | make: archive 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: "Update GitHub Pages" 25 | uses: martinthomson/i-d-template@v1 26 | with: 27 | make: gh-archive 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | 31 | - name: "Save Archive" 32 | uses: actions/upload-artifact@v3 33 | with: 34 | path: archive.json 35 | -------------------------------------------------------------------------------- /.github/workflows/ghpages.yml: -------------------------------------------------------------------------------- 1 | name: "Update Editor's Copy" 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - README.md 7 | - CONTRIBUTING.md 8 | - LICENSE.md 9 | - .gitignore 10 | pull_request: 11 | paths-ignore: 12 | - README.md 13 | - CONTRIBUTING.md 14 | - LICENSE.md 15 | - .gitignore 16 | 17 | jobs: 18 | build: 19 | name: "Update Editor's Copy" 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: "Checkout" 23 | uses: actions/checkout@v2 24 | 25 | - name: "Cache Setup" 26 | id: cache-setup 27 | run: | 28 | mkdir -p "$HOME"/.cache/xml2rfc 29 | echo "::set-output name=path::$HOME/.cache/xml2rfc" 30 | date -u "+::set-output name=date::%FT%T" 31 | 32 | - name: "Cache References" 33 | uses: actions/cache@v2 34 | with: 35 | path: ${{ steps.cache-setup.outputs.path }} 36 | key: refcache-${{ steps.cache-setup.outputs.date }} 37 | restore-keys: | 38 | refcache-${{ steps.cache-setup.outputs.date }} 39 | refcache- 40 | 41 | - name: "Build Drafts" 42 | uses: martinthomson/i-d-template@v1 43 | 44 | - name: "Update GitHub Pages" 45 | uses: martinthomson/i-d-template@v1 46 | if: ${{ github.event_name == 'push' }} 47 | with: 48 | make: gh-pages 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | 52 | - name: "Save HTML" 53 | uses: actions/upload-artifact@v3 54 | with: 55 | path: "*.html" 56 | 57 | - name: "Save Text" 58 | uses: actions/upload-artifact@v3 59 | with: 60 | path: "*.txt" 61 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: "Publish New Draft Version" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "draft-*" 7 | 8 | jobs: 9 | build: 10 | name: "Publish New Draft Version" 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: "Checkout" 14 | uses: actions/checkout@v2 15 | 16 | # See https://github.com/actions/checkout/issues/290 17 | - name: "Get Tag Annotations" 18 | run: git fetch -f origin ${{ github.ref }}:${{ github.ref }} 19 | 20 | - name: "Cache Setup" 21 | id: cache-setup 22 | run: | 23 | mkdir -p "$HOME"/.cache/xml2rfc 24 | echo "::set-output name=path::$HOME/.cache/xml2rfc" 25 | date -u "+::set-output name=date::%FT%T" 26 | 27 | - name: "Cache References" 28 | uses: actions/cache@v2 29 | with: 30 | path: ${{ steps.cache-setup.outputs.path }} 31 | key: refcache-${{ steps.date.outputs.date }} 32 | restore-keys: | 33 | refcache-${{ steps.date.outputs.date }} 34 | refcache- 35 | 36 | - name: "Upload to Datatracker" 37 | uses: martinthomson/i-d-template@v1 38 | with: 39 | make: upload 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.pdf 3 | *.redxml 4 | *.swp 5 | *.txt 6 | *.upload 7 | *~ 8 | .refcache 9 | .tags 10 | .targets.mk 11 | /*-[0-9][0-9].xml 12 | archive.json 13 | report.xml 14 | venv/ 15 | lib 16 | draft-irtf-cfrg-opaque.xml 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "poc/voprf"] 2 | path = poc/voprf 3 | url = git@github.com:cfrg/draft-irtf-cfrg-voprf.git 4 | branch = main 5 | -------------------------------------------------------------------------------- /.note.xml: -------------------------------------------------------------------------------- 1 | 2 | Source for this draft and an issue tracker can be found at 3 | . 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: xenial 3 | 4 | services: 5 | - docker 6 | 7 | env: 8 | DRAFT_DIR: /home/idci/draft 9 | 10 | before_install: 11 | - docker --version 12 | - docker pull martinthomson/i-d-template 13 | 14 | script: 15 | - docker run -d -v "$PWD:/tmp/draft" --tmpfs "$DRAFT_DIR:rw,exec" --name idci 16 | martinthomson/i-d-template sleep 300 17 | - docker exec idci cp -rn /tmp/draft /home/idci 18 | - docker exec -w "$DRAFT_DIR" -e CI=true -e TRAVIS 19 | -e TRAVIS_REPO_SLUG -e TRAVIS_BRANCH -e TRAVIS_TAG -e TRAVIS_PULL_REQUEST 20 | idci make CLONE_ARGS='--reference /home/idci/git-reference' 21 | - docker exec idci ls -l /home/idci/draft/lib 22 | - if [ "${TRAVIS_TAG#draft-}" == "${TRAVIS_TAG}" ]; then 23 | docker exec -w "$DRAFT_DIR" -e CI=true -e GH_TOKEN -e TRAVIS 24 | -e TRAVIS_REPO_SLUG -e TRAVIS_BRANCH -e TRAVIS_TAG -e TRAVIS_PULL_REQUEST 25 | idci make ghpages; 26 | fi 27 | 28 | deploy: 29 | provider: script 30 | script: 31 | - docker exec -w "$DRAFT_DIR" -e CI=true -e GH_TOKEN -e TRAVIS 32 | -e TRAVIS_REPO_SLUG -e TRAVIS_BRANCH -e TRAVIS_TAG -e TRAVIS_PULL_REQUEST 33 | idci make upload 34 | skip_cleanup: true 35 | on: 36 | tags: true 37 | 38 | after_script: 39 | - docker container rm -f idci 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This repository relates to activities in the Internet Engineering Task Force 4 | ([IETF](https://www.ietf.org/)). All material in this repository is considered 5 | Contributions to the IETF Standards Process, as defined in the intellectual 6 | property policies of IETF currently designated as 7 | [BCP 78](https://www.rfc-editor.org/info/bcp78), 8 | [BCP 79](https://www.rfc-editor.org/info/bcp79) and the 9 | [IETF Trust Legal Provisions (TLP) Relating to IETF Documents](http://trustee.ietf.org/trust-legal-provisions.html). 10 | 11 | Any edit, commit, pull request, issue, comment or other change made to this 12 | repository constitutes Contributions to the IETF Standards Process 13 | (https://www.ietf.org/). 14 | 15 | You agree to comply with all applicable IETF policies and procedures, including, 16 | BCP 78, 79, the TLP, and the TLP rules regarding code components (e.g. being 17 | subject to a Simplified BSD License) in Contributions. 18 | 19 | 20 | ## Other Resources 21 | 22 | Discussion of this work occurs on the 23 | [cfrg working group mailing list](https://mailarchive.ietf.org/arch/browse/cfrg/) 24 | ([subscribe](https://www.ietf.org/mailman/listinfo/cfrg)). In addition to 25 | contributions in GitHub, you are encouraged to participate in discussions there. 26 | 27 | **Note**: Some working groups adopt a policy whereby substantive discussion of 28 | technical issues needs to occur on the mailing list. 29 | 30 | You might also like to familiarize yourself with other 31 | [working group documents](https://datatracker.ietf.org/wg/cfrg/documents/). 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | See the 4 | [guidelines for contributions](https://github.com/cfrg/draft-irtf-cfrg-opaque/blob/master/CONTRIBUTING.md). 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LIBDIR := lib 2 | include $(LIBDIR)/main.mk 3 | 4 | $(LIBDIR)/main.mk: 5 | ifneq (,$(shell grep "path *= *$(LIBDIR)" .gitmodules 2>/dev/null)) 6 | git submodule sync 7 | git submodule update $(CLONE_ARGS) --init 8 | else 9 | git clone -q --depth 10 $(CLONE_ARGS) \ 10 | -b main https://github.com/martinthomson/i-d-template $(LIBDIR) 11 | endif 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The OPAQUE Asymmetric PAKE Protocol 2 | 3 | This is the working area for the individual Internet-Draft, "The OPAQUE Asymmetric PAKE Protocol". 4 | 5 | * [Editor's Copy](https://cfrg.github.io/draft-irtf-cfrg-opaque/#go.draft-irtf-cfrg-opaque.html) 6 | * [Individual Draft](https://tools.ietf.org/html/draft-irtf-cfrg-opaque) 7 | * [Compare Editor's Copy to Individual Draft](https://cfrg.github.io/draft-irtf-cfrg-opaque/#go.draft-irtf-cfrg-opaque.diff) 8 | 9 | ## Building the Draft 10 | 11 | Formatted text and HTML versions of the draft can be built using `make`. 12 | 13 | ```sh 14 | $ make 15 | ``` 16 | 17 | This requires that you have the necessary software installed. See 18 | [the instructions](https://github.com/martinthomson/i-d-template/blob/master/doc/SETUP.md). 19 | 20 | 21 | ## Implementations 22 | 23 | | Implementation | Language | Version | Dependencies | 24 | |:--------------------------------------------------------------------------------|:---------|:--------|:-------------| 25 | | [**Reference**](https://github.com/cfrg/draft-irtf-cfrg-opaque/tree/master/poc) | Sage | main | Sage | 26 | | [opaque-ke](https://github.com/novifinancial/opaque-ke) | Rust | main | N/A | 27 | | [opaque](https://github.com/bytemare/opaque/) | Go | main | N/A | 28 | | [libopaque](https://github.com/stef/libopaque) | C | main | N/A | 29 | | [ecc](https://github.com/aldenml/ecc) | C | main | N/A | 30 | | [opaque-ts](https://github.com/cloudflare/opaque-ts) | TypeScript | v07 | [voprf-ts](https://github.com/cloudflare/voprf-ts) | 31 | 32 | ## Contributing 33 | 34 | See the 35 | [guidelines for contributions](https://github.com/cfrg/draft-irtf-cfrg-opaque/blob/master/CONTRIBUTING.md). 36 | -------------------------------------------------------------------------------- /poc/.gitignore: -------------------------------------------------------------------------------- 1 | *.sage.py 2 | __pycache__/ 3 | clear_h_bls12381g2.sage 4 | common.sage 5 | curves.sage 6 | ell2_generic.sage 7 | ell2_opt_3mod4.sage 8 | ell2_opt_5mod8.sage 9 | ell2edw_generic.sage 10 | generic_map.sage 11 | h2c_suite.sage 12 | hash_to_field.py 13 | iso_values.sage 14 | map_check.sage 15 | sagelib/ 16 | sqrt.sage 17 | sswu_generic.sage 18 | sswu_optimized.sage 19 | sswu_opt_3mod4.sage 20 | sswu_opt_5mod8.sage 21 | sswu_opt_9mod16.sage 22 | suite_25519.sage 23 | suite_448.sage 24 | suite_bls12381g1.sage 25 | suite_bls12381g2.sage 26 | suite_p256.sage 27 | suite_p384.sage 28 | suite_p521.sage 29 | suite_secp256k1.sage 30 | svdw_generic.sage 31 | test.sage 32 | test_vectors.sage 33 | z_selection.sage 34 | z_values.sage 35 | oprf.sage 36 | test_oprf.sage 37 | groups.sage 38 | ristretto_decaf.sage 39 | -------------------------------------------------------------------------------- /poc/Makefile: -------------------------------------------------------------------------------- 1 | SAGEFILES := $(basename $(notdir $(wildcard *.sage))) 2 | PYFILES := $(addprefix sagelib/, $(addsuffix .py,$(SAGEFILES))) 3 | .PRECIOUS: $(PYFILES) 4 | 5 | .PHONY: pyfiles 6 | pyfiles: sagelib/__init__.py $(PYFILES) 7 | 8 | sagelib/__init__.py: 9 | mkdir -p sagelib 10 | echo pass > sagelib/__init__.py 11 | 12 | sagelib/%.py: %.sage 13 | @echo "Parsing $<" 14 | @sage --preparse $< 15 | @mv $<.py $@ 16 | 17 | setup: 18 | cp voprf/poc/h2c/poc/hash_to_field.py . 19 | cp voprf/poc/h2c/poc/*.sage . 20 | cp voprf/poc/*.sage . 21 | 22 | test: pyfiles 23 | @mkdir -p vectors 24 | sage test_opaque_ake.sage 25 | 26 | vectors: pyfiles 27 | @mkdir -p vectors 28 | sage test_opaque_ake.sage 29 | python3 format_test_vectors.py vectors/vectors.json > vectors/formatted.txt 30 | 31 | .PHONY: clean 32 | clean: 33 | rm -rf sagelib *.pyc *.sage.py *.log __pycache__ 34 | 35 | .PHONY: distclean 36 | distclean: clean 37 | rm -rf vectors ascii 38 | -------------------------------------------------------------------------------- /poc/ake_group.sage: -------------------------------------------------------------------------------- 1 | import sys 2 | import hashlib 3 | 4 | ########## Definitions from RFC 7748 ################## 5 | from sagelib.rfc7748 import * 6 | from sagelib.groups import * 7 | from sagelib.string_utils import * 8 | 9 | try: 10 | from sagelib.opaque_common import curve25519_clamp 11 | except ImportError as e: 12 | sys.exit("Error loading preprocessed sage files. Try running `make setup && make clean pyfiles`. Full error: " + e) 13 | 14 | 15 | class GroupCurve25519(Group): 16 | def __init__(self): 17 | Group.__init__(self, "curve25519") 18 | 19 | def generator(self): 20 | return IntegerToByteArray(9) 21 | 22 | def serialize(self, element): 23 | # Curve25519 points are bytes 24 | return element 25 | 26 | def deserialize(self, encoded): 27 | # Curve25519 points are bytes 28 | return encoded 29 | 30 | def serialize_scalar(self, scalar): 31 | # Curve25519 scalars are represented as bytes 32 | return scalar 33 | 34 | def element_byte_length(self): 35 | return 32 36 | 37 | def scalar_byte_length(self): 38 | return 32 39 | 40 | def random_scalar(self, rng): 41 | return curve25519_clamp(rng.random_bytes(32)) 42 | 43 | def scalar_mult(self, x, y): 44 | return X25519(x, y) 45 | 46 | def __str__(self): 47 | return self.name 48 | 49 | if __name__ == "__main__": 50 | # From RFC7748: https://www.rfc-editor.org/rfc/rfc7748#section-6.1 51 | a = bytes.fromhex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a") 52 | A = bytes.fromhex("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a") 53 | G = GroupCurve25519() 54 | A_exp = G.scalar_mult(a, G.generator()) 55 | assert(A_exp == A) -------------------------------------------------------------------------------- /poc/format_test_vectors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # vim: syntax=python 3 | 4 | import sys 5 | import json 6 | 7 | config_keys = [ 8 | "OPRF", 9 | "Hash", 10 | "KSF", 11 | "KDF", 12 | "MAC", 13 | "Group", 14 | "Context", 15 | "Nh", 16 | "Npk", 17 | "Nsk", 18 | "Nm", 19 | "Nx", 20 | "Nok", 21 | ] 22 | 23 | input_keys = [ 24 | "client_identity", 25 | "server_identity", 26 | "oprf_seed", 27 | "credential_identifier", 28 | "password", 29 | "envelope_nonce", 30 | "masking_nonce", 31 | "client_private_key", 32 | "server_private_key", 33 | "server_public_key", 34 | "server_nonce", 35 | "client_nonce", 36 | "client_keyshare_seed", 37 | "server_keyshare_seed", 38 | "blind_registration", 39 | "blind_login", 40 | ] 41 | 42 | intermediate_keys = [ 43 | "client_public_key", 44 | "auth_key", 45 | "randomized_password", 46 | "pseudorandom_pad", 47 | "envelope", 48 | "handshake_secret", 49 | "server_mac_key", 50 | "client_mac_key", 51 | "oprf_key", 52 | ] 53 | 54 | output_keys = [ 55 | "registration_request", 56 | "registration_response", 57 | "registration_upload", 58 | "KE1", 59 | "KE2", 60 | "KE3", 61 | "export_key", 62 | "session_key", 63 | ] 64 | 65 | # Fake Vector Keys 66 | 67 | fake_input_keys = [ 68 | "client_identity", 69 | "server_identity", 70 | "oprf_seed", 71 | "credential_identifier", 72 | "password", 73 | "envelope_nonce", 74 | "masking_nonce", 75 | "client_private_key", 76 | "client_public_key", 77 | "server_private_key", 78 | "server_public_key", 79 | "server_nonce", 80 | "client_nonce", 81 | "client_keyshare_seed", 82 | "server_keyshare_seed", 83 | "blind_registration", 84 | "blind_login", 85 | "masking_key", 86 | "KE1", 87 | ] 88 | 89 | fake_output_keys = [ 90 | "KE2", 91 | ] 92 | 93 | 94 | def to_hex(octet_string): 95 | if isinstance(octet_string, str): 96 | return "".join("{:02x}".format(ord(c)) for c in octet_string) 97 | if isinstance(octet_string, bytes): 98 | return "" + "".join("{:02x}".format(c) for c in octet_string) 99 | assert isinstance(octet_string, bytearray) 100 | return ''.join(format(x, '02x') for x in octet_string) 101 | 102 | 103 | def wrap_print(arg, *args): 104 | line_length = 69 105 | string = arg + " " + " ".join(args) 106 | for hunk in (string[0+i:line_length+i] for i in range(0, len(string), line_length)): 107 | if hunk and len(hunk.strip()) > 0: 108 | print(hunk) 109 | 110 | 111 | def format_vector_name(vector): 112 | return "OPAQUE-" + vector["config"]["Name"] 113 | 114 | 115 | def print_vector_config(vector): 116 | for key in config_keys: 117 | for config_key in vector["config"]: 118 | if key == config_key: 119 | wrap_print(key + ":", vector["config"][key]) 120 | 121 | 122 | def print_vector_inputs(arr, vector): 123 | for key in arr: 124 | for input_key in vector["inputs"]: 125 | if key == input_key: 126 | wrap_print(key + ":", vector["inputs"][key]) 127 | 128 | 129 | def print_vector_intermediates(arr, vector): 130 | for key in arr: 131 | for int_key in vector["intermediates"]: 132 | if key == int_key: 133 | wrap_print(key + ":", vector["intermediates"][key]) 134 | 135 | 136 | def print_vector_outputs(arr, vector): 137 | for key in arr: 138 | for output_key in vector["outputs"]: 139 | if key == output_key: 140 | wrap_print(key + ":", vector["outputs"][key]) 141 | 142 | 143 | def format_vector(vector, i): 144 | print("\n#### Configuration\n") 145 | print("~~~") 146 | print_vector_config(vector) 147 | print("~~~") 148 | print("\n#### Input Values\n") 149 | print("~~~") 150 | print_vector_inputs(input_keys, vector) 151 | print("~~~") 152 | print("\n#### Intermediate Values\n") 153 | print("~~~") 154 | print_vector_intermediates(intermediate_keys, vector) 155 | print("~~~") 156 | print("\n#### Output Values\n") 157 | print("~~~") 158 | print_vector_outputs(output_keys, vector) 159 | print("~~~") 160 | print("") 161 | 162 | 163 | def format_fake_vector(vector, i): 164 | print("\n#### Configuration\n") 165 | print("~~~") 166 | print_vector_config(vector) 167 | print("~~~") 168 | print("\n#### Input Values\n") 169 | print("~~~") 170 | print_vector_inputs(fake_input_keys, vector) 171 | print("~~~") 172 | print("\n#### Output Values\n") 173 | print("~~~") 174 | print_vector_outputs(fake_output_keys, vector) 175 | print("~~~") 176 | print("") 177 | 178 | 179 | with open(sys.argv[1], "r") as fh: 180 | vectors = json.loads(fh.read()) 181 | real_vectors = [] 182 | fake_vectors = [] 183 | for i, vector in enumerate(vectors): 184 | if vector["config"]["Fake"] == "True": 185 | fake_vectors.append(vector) 186 | else: 187 | real_vectors.append(vector) 188 | print("## Real Test Vectors {#real-vectors}\n") 189 | for i, vector in enumerate(real_vectors): 190 | print("### " + format_vector_name(vector) + 191 | " Real Test Vector " + str(i+1)) 192 | format_vector(vector, i) 193 | 194 | print("## Fake Test Vectors {#fake-vectors}\n") 195 | for i, vector in enumerate(fake_vectors): 196 | print("### " + format_vector_name(vector) + 197 | " Fake Test Vector " + str(i+1)) 198 | format_fake_vector(vector, i) 199 | -------------------------------------------------------------------------------- /poc/hash.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2014 Richard Moore 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. 22 | 23 | 24 | import hashlib 25 | import hmac 26 | import struct 27 | 28 | 29 | # Python 2 30 | if bytes == str: 31 | def check_bytes(byte_array): 32 | return True 33 | 34 | def get_byte(c): 35 | 'Converts a 1-byte string to a byte' 36 | return ord(c) 37 | 38 | def chars_to_bytes(array): 39 | 'Converts an array of integers to an array of bytes.' 40 | return ''.join(chr(c) for c in array) 41 | 42 | # Python 3 43 | else: 44 | xrange = range 45 | 46 | def check_bytes(byte_array): 47 | return isinstance(byte_array, bytes) 48 | 49 | def get_byte(c): 50 | return c 51 | 52 | def chars_to_bytes(array): 53 | return bytes(array) 54 | 55 | 56 | def pbkdf2_single(password, salt, key_length, prf): 57 | '''Returns the result of the Password-Based Key Derivation Function 2 with 58 | a single iteration (i.e. count = 1). 59 | 60 | prf - a psuedorandom function 61 | 62 | See http://en.wikipedia.org/wiki/PBKDF2 63 | ''' 64 | 65 | block_number = 0 66 | result = b'' 67 | 68 | # The iterations 69 | while len(result) < key_length: 70 | block_number += 1 71 | result += prf(password, salt + struct.pack('>L', block_number)) 72 | 73 | return result[:key_length] 74 | 75 | 76 | def salsa20_8(B): 77 | '''Salsa 20/8 stream cypher; Used by BlockMix. See http://en.wikipedia.org/wiki/Salsa20''' 78 | 79 | # Create a working copy 80 | x = B[:] 81 | 82 | # Expanded form of this code. The expansion is significantly faster but 83 | # this is much easier to understand 84 | # ROUNDS = ( 85 | # (4, 0, 12, 7), (8, 4, 0, 9), (12, 8, 4, 13), (0, 12, 8, 18), 86 | # (9, 5, 1, 7), (13, 9, 5, 9), (1, 13, 9, 13), (5, 1, 13, 18), 87 | # (14, 10, 6, 7), (2, 14, 10, 9), (6, 2, 14, 13), (10, 6, 2, 18), 88 | # (3, 15, 11, 7), (7, 3, 15, 9), (11, 7, 3, 13), (15, 11, 7, 18), 89 | # (1, 0, 3, 7), (2, 1, 0, 9), (3, 2, 1, 13), (0, 3, 2, 18), 90 | # (6, 5, 4, 7), (7, 6, 5, 9), (4, 7, 6, 13), (5, 4, 7, 18), 91 | # (11, 10, 9, 7), (8, 11, 10, 9), (9, 8, 11, 13), (10, 9, 8, 18), 92 | # (12, 15, 14, 7), (13, 12, 15, 9), (14, 13, 12, 13), (15, 14, 13, 18), 93 | # ) 94 | # 95 | # for (destination, a1, a2, b) in ROUNDS: 96 | # a = (x[a1] + x[a2]) & 0xffffffff 97 | # x[destination] ^= ((a << b) | (a >> (32 - b))) & 0xffffffff 98 | for i in (8, 6, 4, 2): 99 | a = (x[0] + x[12]) & 0xffffffff 100 | x[4] ^= ((a << 7) | (a >> 25)) 101 | a = (x[4] + x[0]) & 0xffffffff 102 | x[8] ^= ((a << 9) | (a >> 23)) 103 | a = (x[8] + x[4]) & 0xffffffff 104 | x[12] ^= ((a << 13) | (a >> 19)) 105 | a = (x[12] + x[8]) & 0xffffffff 106 | x[0] ^= ((a << 18) | (a >> 14)) 107 | a = (x[5] + x[1]) & 0xffffffff 108 | x[9] ^= ((a << 7) | (a >> 25)) 109 | a = (x[9] + x[5]) & 0xffffffff 110 | x[13] ^= ((a << 9) | (a >> 23)) 111 | a = (x[13] + x[9]) & 0xffffffff 112 | x[1] ^= ((a << 13) | (a >> 19)) 113 | a = (x[1] + x[13]) & 0xffffffff 114 | x[5] ^= ((a << 18) | (a >> 14)) 115 | a = (x[10] + x[6]) & 0xffffffff 116 | x[14] ^= ((a << 7) | (a >> 25)) 117 | a = (x[14] + x[10]) & 0xffffffff 118 | x[2] ^= ((a << 9) | (a >> 23)) 119 | a = (x[2] + x[14]) & 0xffffffff 120 | x[6] ^= ((a << 13) | (a >> 19)) 121 | a = (x[6] + x[2]) & 0xffffffff 122 | x[10] ^= ((a << 18) | (a >> 14)) 123 | a = (x[15] + x[11]) & 0xffffffff 124 | x[3] ^= ((a << 7) | (a >> 25)) 125 | a = (x[3] + x[15]) & 0xffffffff 126 | x[7] ^= ((a << 9) | (a >> 23)) 127 | a = (x[7] + x[3]) & 0xffffffff 128 | x[11] ^= ((a << 13) | (a >> 19)) 129 | a = (x[11] + x[7]) & 0xffffffff 130 | x[15] ^= ((a << 18) | (a >> 14)) 131 | a = (x[0] + x[3]) & 0xffffffff 132 | x[1] ^= ((a << 7) | (a >> 25)) 133 | a = (x[1] + x[0]) & 0xffffffff 134 | x[2] ^= ((a << 9) | (a >> 23)) 135 | a = (x[2] + x[1]) & 0xffffffff 136 | x[3] ^= ((a << 13) | (a >> 19)) 137 | a = (x[3] + x[2]) & 0xffffffff 138 | x[0] ^= ((a << 18) | (a >> 14)) 139 | a = (x[5] + x[4]) & 0xffffffff 140 | x[6] ^= ((a << 7) | (a >> 25)) 141 | a = (x[6] + x[5]) & 0xffffffff 142 | x[7] ^= ((a << 9) | (a >> 23)) 143 | a = (x[7] + x[6]) & 0xffffffff 144 | x[4] ^= ((a << 13) | (a >> 19)) 145 | a = (x[4] + x[7]) & 0xffffffff 146 | x[5] ^= ((a << 18) | (a >> 14)) 147 | a = (x[10] + x[9]) & 0xffffffff 148 | x[11] ^= ((a << 7) | (a >> 25)) 149 | a = (x[11] + x[10]) & 0xffffffff 150 | x[8] ^= ((a << 9) | (a >> 23)) 151 | a = (x[8] + x[11]) & 0xffffffff 152 | x[9] ^= ((a << 13) | (a >> 19)) 153 | a = (x[9] + x[8]) & 0xffffffff 154 | x[10] ^= ((a << 18) | (a >> 14)) 155 | a = (x[15] + x[14]) & 0xffffffff 156 | x[12] ^= ((a << 7) | (a >> 25)) 157 | a = (x[12] + x[15]) & 0xffffffff 158 | x[13] ^= ((a << 9) | (a >> 23)) 159 | a = (x[13] + x[12]) & 0xffffffff 160 | x[14] ^= ((a << 13) | (a >> 19)) 161 | a = (x[14] + x[13]) & 0xffffffff 162 | x[15] ^= ((a << 18) | (a >> 14)) 163 | 164 | 165 | # Add the original values 166 | for i in xrange(0, 16): 167 | B[i] = (B[i] + x[i]) & 0xffffffff 168 | 169 | 170 | def blockmix_salsa8(BY, Yi, r): 171 | '''Blockmix; Used by SMix.''' 172 | 173 | start = (2 * r - 1) * 16 174 | X = BY[start:start + 16] # BlockMix - 1 175 | 176 | for i in xrange(0, 2 * r): # BlockMix - 2 177 | 178 | for xi in xrange(0, 16): # BlockMix - 3(inner) 179 | X[xi] ^= BY[i * 16 + xi] 180 | 181 | salsa20_8(X) # BlockMix - 3(outer) 182 | aod = Yi + i * 16 # BlockMix - 4 183 | BY[aod:aod + 16] = X[:16] 184 | 185 | for i in xrange(0, r): # BlockMix - 6 (and below) 186 | aos = Yi + i * 32 187 | aod = i * 16 188 | BY[aod:aod + 16] = BY[aos:aos + 16] 189 | 190 | for i in xrange(0, r): 191 | aos = Yi + (i * 2 + 1) * 16 192 | aod = (i + r) * 16 193 | BY[aod:aod + 16] = BY[aos:aos + 16] 194 | 195 | 196 | def smix(B, Bi, r, N, V, X): 197 | '''SMix; a specific case of ROMix. See scrypt.pdf in the links above.''' 198 | 199 | X[:32 * r] = B[Bi:Bi + 32 * r] # ROMix - 1 200 | 201 | for i in xrange(0, N): # ROMix - 2 202 | aod = i * 32 * r # ROMix - 3 203 | V[aod:aod + 32 * r] = X[:32 * r] 204 | blockmix_salsa8(X, 32 * r, r) # ROMix - 4 205 | 206 | for i in xrange(0, N): # ROMix - 6 207 | j = X[(2 * r - 1) * 16] & (N - 1) # ROMix - 7 208 | for xi in xrange(0, 32 * r): # ROMix - 8(inner) 209 | X[xi] ^= V[j * 32 * r + xi] 210 | 211 | blockmix_salsa8(X, 32 * r, r) # ROMix - 9(outer) 212 | 213 | B[Bi:Bi + 32 * r] = X[:32 * r] # ROMix - 10 214 | 215 | 216 | 217 | def hash(password, salt, N, r, p, dkLen): 218 | """Returns the result of the scrypt password-based key derivation function. 219 | 220 | Constraints: 221 | r * p < (2 ** 30) 222 | dkLen <= (((2 ** 32) - 1) * 32 223 | N must be a power of 2 greater than 1 (eg. 2, 4, 8, 16, 32...) 224 | N, r, p must be positive 225 | """ 226 | 227 | # This only matters to Python 3 228 | if not check_bytes(password): 229 | raise ValueError('password must be a byte array') 230 | 231 | if not check_bytes(salt): 232 | raise ValueError('salt must be a byte array') 233 | 234 | # Scrypt implementation. Significant thanks to https://github.com/wg/scrypt 235 | if N < 2 or (N & (N - 1)): raise ValueError('Scrypt N must be a power of 2 greater than 1') 236 | 237 | # A psuedorandom function 238 | prf = lambda k, m: hmac.new(key = k, msg = m, digestmod = hashlib.sha256).digest() 239 | 240 | # convert into integers 241 | B = [ get_byte(c) for c in pbkdf2_single(password, salt, p * 128 * r, prf) ] 242 | B = [ ((B[i + 3] << 24) | (B[i + 2] << 16) | (B[i + 1] << 8) | B[i + 0]) for i in xrange(0, len(B), 4)] 243 | 244 | XY = [ 0 ] * (64 * r) 245 | V = [ 0 ] * (32 * r * N) 246 | 247 | for i in xrange(0, p): 248 | smix(B, i * 32 * r, r, N, V, XY) 249 | 250 | # Convert back into bytes 251 | Bc = [ ] 252 | for i in B: 253 | Bc.append((i >> 0) & 0xff) 254 | Bc.append((i >> 8) & 0xff) 255 | Bc.append((i >> 16) & 0xff) 256 | Bc.append((i >> 24) & 0xff) 257 | 258 | return pbkdf2_single(password, chars_to_bytes(Bc), dkLen, prf) 259 | 260 | def scrypt(password, salt, N, r, p, dklen): 261 | return hash(password, salt, N, r, p, dklen) -------------------------------------------------------------------------------- /poc/opaque_ake.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | import hmac 6 | 7 | from collections import namedtuple 8 | 9 | try: 10 | from sagelib.opaque_common import derive_secret, hkdf_expand_label, hkdf_extract, I2OSP, OS2IP, OS2IP_le, encode_vector, encode_vector_len, to_hex, OPAQUE_NONCE_LENGTH 11 | from sagelib.opaque_core import OPAQUECore, OPAQUE_SEED_LENGTH 12 | from sagelib.opaque_messages import deserialize_credential_request, deserialize_credential_response 13 | except ImportError as e: 14 | sys.exit("Error loading preprocessed sage files. Try running `make setup && make clean pyfiles`. Full error: " + e) 15 | 16 | _as_bytes = lambda x: x if isinstance(x, bytes) else bytes(x, "utf-8") 17 | 18 | class Configuration(object): 19 | def __init__(self, oprf_suite, kdf, mac, hash, ksf, group, context): 20 | self.oprf_suite = oprf_suite 21 | self.kdf = kdf 22 | self.mac = mac 23 | self.hash = hash 24 | self.ksf = ksf 25 | self.group = group 26 | self.context = context 27 | self.Npk = group.element_byte_length() 28 | self.Nsk = group.scalar_byte_length() 29 | self.Nm = mac.output_size() 30 | self.Nx = hash().digest_size 31 | self.Nok = oprf_suite.group.scalar_byte_length() 32 | self.Nh = hash().digest_size 33 | self.Nn = OPAQUE_NONCE_LENGTH 34 | self.Ne = self.Nn + self.Nm 35 | 36 | class KeyExchange(object): 37 | def __init__(self): 38 | pass 39 | 40 | def json(self): 41 | raise Exception("Not implemented") 42 | 43 | def generate_ke1(self, l1): 44 | raise Exception("Not implemented") 45 | 46 | def generate_ke2(self, l1, l2, ke1, client_public_key, server_private_key, server_public_key): 47 | raise Exception("Not implemented") 48 | 49 | def generate_ke3(self, l2, ake2, ke1_state, server_public_key, client_private_key, client_public_key): 50 | raise Exception("Not implemented") 51 | 52 | TripleDHComponents = namedtuple("TripleDHComponents", "pk1 sk1 pk2 sk2 pk3 sk3") 53 | 54 | class OPAQUE3DH(KeyExchange): 55 | def __init__(self, config, rng): 56 | self.config = config 57 | self.core = OPAQUECore(config, rng) 58 | self.rng = rng 59 | 60 | def json(self): 61 | return { 62 | "Name": "3DH", 63 | "Group": self.config.group.name, 64 | "OPRF": self.config.oprf_suite.identifier, 65 | "KDF": self.config.kdf.name, 66 | "MAC": self.config.mac.name, 67 | "Hash": self.config.hash().name.upper(), 68 | "KSF": self.config.ksf.name, 69 | "Context": to_hex(self.config.context), 70 | "Nh": str(self.config.Nh), 71 | "Npk": str(self.config.Npk), 72 | "Nsk": str(self.config.Nsk), 73 | "Nm": str(self.config.Nm), 74 | "Nx": str(self.config.Nx), 75 | "Nok": str(self.config.Nok), 76 | } 77 | 78 | def derive_3dh_keys(self, dh_components, info): 79 | dh1 = self.config.group.scalar_mult(dh_components.sk1, self.config.group.deserialize(dh_components.pk1)) 80 | dh2 = self.config.group.scalar_mult(dh_components.sk2, self.config.group.deserialize(dh_components.pk2)) 81 | dh3 = self.config.group.scalar_mult(dh_components.sk3, self.config.group.deserialize(dh_components.pk3)) 82 | 83 | dh1_encoded = self.config.group.serialize(dh1) 84 | dh2_encoded = self.config.group.serialize(dh2) 85 | dh3_encoded = self.config.group.serialize(dh3) 86 | ikm = dh1_encoded + dh2_encoded + dh3_encoded 87 | 88 | prk = hkdf_extract(self.config, bytes([]), ikm) 89 | handshake_secret = derive_secret(self.config, prk, _as_bytes("HandshakeSecret"), info) 90 | session_key = derive_secret(self.config, prk, _as_bytes("SessionKey"), info) 91 | 92 | # client_mac_key = HKDF-Expand-Label(handshake_secret, "ClientMAC", "", Hash.length) 93 | # server_mac_key = HKDF-Expand-Label(handshake_secret, "ServerMAC", "", Hash.length) 94 | # handshake_encrypt_key = HKDF-Expand-Label(handshake_secret, "HandshakeKey", "", key_length) 95 | Nh = self.config.hash().digest_size 96 | empty_info = bytes([]) 97 | server_mac_key = hkdf_expand_label(self.config, handshake_secret, _as_bytes("ServerMAC"), empty_info, Nh) 98 | client_mac_key = hkdf_expand_label(self.config, handshake_secret, _as_bytes("ClientMAC"), empty_info, Nh) 99 | 100 | return server_mac_key, client_mac_key, session_key, handshake_secret 101 | 102 | def auth_client_start(self): 103 | self.client_nonce = self.rng.random_bytes(OPAQUE_NONCE_LENGTH) 104 | self.client_keyshare_seed = self.rng.random_bytes(OPAQUE_SEED_LENGTH) 105 | self.client_private_keyshare, self.client_public_keyshare = self.core.derive_diffie_hellman_key_pair(self.client_keyshare_seed) 106 | return TripleDHMessageInit(self.client_nonce, self.client_public_keyshare) 107 | 108 | def generate_ke1(self, password): 109 | cred_request, cred_metadata = self.core.create_credential_request(password) 110 | self.serialized_request = cred_request.serialize() 111 | self.cred_metadata = cred_metadata 112 | self.password = password 113 | 114 | ke1 = self.auth_client_start() 115 | 116 | return self.serialized_request + ke1.serialize() 117 | 118 | def transcript_hasher(self, serialized_request, serialized_response, cleartext_credentials, client_nonce, client_public_keyshare, server_nonce, server_public_keyshare_bytes): 119 | hasher = self.config.hash() 120 | hasher.update(_as_bytes("OPAQUEv1-")) # OPAQUEv1- 121 | hasher.update(encode_vector(self.config.context)) # context 122 | hasher.update(encode_vector_len(cleartext_credentials.client_identity, 2)) # client_identity 123 | hasher.update(serialized_request) # ke1: cred request 124 | hasher.update(client_nonce) # ke1: client nonce 125 | hasher.update(client_public_keyshare) # ke1: client keyshare 126 | hasher.update(encode_vector_len(cleartext_credentials.server_identity, 2)) # server identity 127 | hasher.update(serialized_response) # ke2: cred response 128 | hasher.update(server_nonce) # ke2: server nonce 129 | hasher.update(server_public_keyshare_bytes) # ke2: server keyshare 130 | 131 | self.hasher = hasher 132 | 133 | return hasher.digest() 134 | 135 | def auth_server_respond(self, cred_request, cred_response, ke1, cleartext_credentials, server_private_key, client_public_keyshare): 136 | self.server_nonce = self.rng.random_bytes(OPAQUE_NONCE_LENGTH) 137 | self.server_keyshare_seed = self.rng.random_bytes(OPAQUE_SEED_LENGTH) 138 | self.server_private_keyshare, self.server_public_keyshare_bytes = self.core.derive_diffie_hellman_key_pair(self.server_keyshare_seed) 139 | 140 | transcript_hash = self.transcript_hasher(cred_request.serialize(), cred_response.serialize(), cleartext_credentials, ke1.client_nonce, ke1.client_public_keyshare, self.server_nonce, self.server_public_keyshare_bytes) 141 | 142 | # K3dh = epkU^eskS || epkU^skS || pkU^eskS 143 | dh_components = TripleDHComponents(ke1.client_public_keyshare, self.server_private_keyshare, ke1.client_public_keyshare, server_private_key, client_public_keyshare, self.server_private_keyshare) 144 | 145 | server_mac_key, client_mac_key, session_key, handshake_secret = self.derive_3dh_keys(dh_components, self.hasher.digest()) 146 | mac = hmac.digest(server_mac_key, transcript_hash, self.config.hash) 147 | ake2 = TripleDHMessageRespond(self.server_nonce, self.server_public_keyshare_bytes, mac) 148 | 149 | self.server_mac_key = server_mac_key 150 | self.ake2 = ake2 151 | self.client_mac_key = client_mac_key 152 | self.session_key = session_key 153 | self.server_mac = mac 154 | self.handshake_secret = handshake_secret 155 | 156 | return ake2 157 | 158 | def generate_ke2(self, msg, oprf_seed, credential_identifier, envU, masking_key, server_identity, server_private_key, server_public_keyshare, client_identity, client_public_keyshare): 159 | cred_request, offset = deserialize_credential_request(self.config, msg) 160 | ke1 = deserialize_tripleDH_init(self.config, msg[offset:]) 161 | 162 | cred_response = self.core.create_credential_response(cred_request, server_public_keyshare, oprf_seed, envU, credential_identifier, masking_key) 163 | serialized_response = cred_response.serialize() 164 | self.masking_nonce = cred_response.masking_nonce 165 | 166 | cleartext_credentials = self.core.create_cleartext_credentials(server_public_keyshare, client_public_keyshare, server_identity, client_identity) 167 | ake2 = self.auth_server_respond(cred_request, cred_response, ke1, cleartext_credentials, server_private_key, client_public_keyshare) 168 | 169 | return serialized_response + ake2.serialize() 170 | 171 | def auth_client_finalize(self, cred_response, ake2, cleartext_credentials, client_private_key): 172 | transcript_hash = self.transcript_hasher(self.serialized_request, cred_response.serialize(), cleartext_credentials, self.client_nonce, self.client_public_keyshare, ake2.server_nonce, ake2.server_public_keyshare_bytes) 173 | 174 | # K3dh = epkS^eskU || pkS^eskU || epkS^skU 175 | dh_components = TripleDHComponents(ake2.server_public_keyshare_bytes, self.client_private_keyshare, cleartext_credentials.server_public_key_bytes, self.client_private_keyshare, ake2.server_public_keyshare_bytes, client_private_key) 176 | 177 | server_mac_key, client_mac_key, session_key, handshake_secret = self.derive_3dh_keys(dh_components, self.hasher.digest()) 178 | server_mac = hmac.digest(server_mac_key, transcript_hash, self.config.hash) 179 | assert server_mac == ake2.mac 180 | 181 | self.session_key = session_key 182 | self.server_mac_key = server_mac_key 183 | self.client_mac_key = client_mac_key 184 | self.handshake_secret = handshake_secret 185 | 186 | # transcript3 == transcript2, plus server_mac 187 | self.hasher.update(server_mac) 188 | transcript_hash = self.hasher.digest() 189 | 190 | client_mac = hmac.digest(client_mac_key, transcript_hash, self.config.hash) 191 | 192 | return TripleDHMessageFinish(client_mac) 193 | 194 | def generate_ke3(self, msg, client_identity, server_identity): 195 | cred_response, offset = deserialize_credential_response(self.config, msg) 196 | ake2 = deserialize_tripleDH_respond(self.config, msg[offset:]) 197 | client_private_key_bytes, cleartext_credentials, export_key = self.core.recover_credentials(self.password, self.cred_metadata, cred_response, client_identity, server_identity) 198 | 199 | if "curve25519" in self.config.group.name: 200 | client_private_key = client_private_key_bytes 201 | elif "ristretto" in self.config.group.name or "decaf" in self.config.group.name: 202 | client_private_key = OS2IP_le(client_private_key_bytes) 203 | else: 204 | client_private_key = OS2IP(client_private_key_bytes) 205 | 206 | self.export_key = export_key 207 | ke3 = self.auth_client_finalize(cred_response, ake2, cleartext_credentials, client_private_key) 208 | 209 | return ke3.serialize() 210 | 211 | def auth_server_finish(self, msg): 212 | ke3 = deserialize_tripleDH_finish(self.config, msg) 213 | 214 | client_mac_key = self.client_mac_key 215 | self.hasher.update(self.server_mac) 216 | transcript_hash = self.hasher.digest() 217 | 218 | client_mac = hmac.digest(client_mac_key, transcript_hash, self.config.hash) 219 | assert client_mac == ke3.mac 220 | 221 | return self.session_key 222 | 223 | # struct { 224 | # opaque client_nonce[32]; 225 | # opaque client_public_keyshare[LK]; 226 | # } KE1M; 227 | def deserialize_tripleDH_init(config, data): 228 | client_nonce = data[0:OPAQUE_NONCE_LENGTH] 229 | client_public_keyshare = data[OPAQUE_NONCE_LENGTH:] 230 | length = config.oprf_suite.group.element_byte_length() 231 | if len(client_public_keyshare) != length: 232 | raise Exception("Invalid client_public_keyshare length: %d %d" % (len(client_public_keyshare), length)) 233 | return TripleDHMessageInit(client_nonce, client_public_keyshare) 234 | 235 | class TripleDHMessageInit(object): 236 | def __init__(self, client_nonce, client_public_keyshare): 237 | self.client_nonce = client_nonce 238 | self.client_public_keyshare = client_public_keyshare 239 | 240 | def serialize(self): 241 | return self.client_nonce + self.client_public_keyshare 242 | 243 | # struct { 244 | # opaque server_nonce[32]; 245 | # opaque server_public_keyshare[LK]; 246 | # opaque mac[LH]; 247 | # } KE2M; 248 | def deserialize_tripleDH_respond(config, data): 249 | length = config.oprf_suite.group.element_byte_length() 250 | server_nonce = data[0:OPAQUE_NONCE_LENGTH] 251 | server_public_keyshare_bytes = data[OPAQUE_NONCE_LENGTH:OPAQUE_NONCE_LENGTH+length] 252 | mac = data[OPAQUE_NONCE_LENGTH+length:] 253 | if len(mac) != config.hash().digest_size: 254 | raise Exception("Invalid MAC length: %d %d" % (len(mac), config.hash().digest_size)) 255 | return TripleDHMessageRespond(server_nonce, server_public_keyshare_bytes, mac) 256 | 257 | class TripleDHMessageRespond(object): 258 | def __init__(self, server_nonce, server_public_keyshare_bytes, mac): 259 | self.server_nonce = server_nonce 260 | self.server_public_keyshare_bytes = server_public_keyshare_bytes 261 | self.mac = mac 262 | 263 | def serialize(self): 264 | return self.server_nonce + self.server_public_keyshare_bytes + self.mac 265 | 266 | # struct { 267 | # opaque mac[LH]; 268 | # } KE3M; 269 | def deserialize_tripleDH_finish(config, data): 270 | if len(data) != config.hash().digest_size: 271 | raise Exception("Invalid MAC length: %d %d" % (len(data), config.hash().digest_size)) 272 | return TripleDHMessageFinish(data) 273 | 274 | class TripleDHMessageFinish(object): 275 | def __init__(self, mac): 276 | self.mac = mac 277 | 278 | def serialize(self): 279 | return self.mac 280 | -------------------------------------------------------------------------------- /poc/opaque_common.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import hmac 5 | import struct 6 | 7 | def _as_bytes(x): return x if isinstance(x, bytes) else bytes(x, "utf-8") 8 | 9 | OPAQUE_NONCE_LENGTH = 32 10 | 11 | def to_hex(octet_string): 12 | if isinstance(octet_string, str): 13 | return "".join("{:02x}".format(ord(c)) for c in octet_string) 14 | if isinstance(octet_string, bytes): 15 | return "" + "".join("{:02x}".format(c) for c in octet_string) 16 | assert isinstance(octet_string, bytearray) 17 | return ''.join(format(x, '02x') for x in octet_string) 18 | 19 | def zero_bytes(n): 20 | return bytearray([0] * n) 21 | 22 | def xor(a, b): 23 | if len(a) != len(b): 24 | print(len(a), len(b)) 25 | assert len(a) == len(b) 26 | c = bytearray(a) 27 | for i, v in enumerate(b): 28 | c[i] = c[i] ^^ v # bitwise XOR 29 | return bytes(c) 30 | 31 | # Performs the curve25519 clamping operation 32 | def curve25519_clamp(scalar): 33 | arr = bytearray(scalar) 34 | arr[0] &= 248 35 | arr[31] &= 127 36 | arr[31] |= 64 37 | return bytes(arr) 38 | 39 | def hkdf_extract(config, salt, ikm): 40 | return hmac.digest(salt, ikm, config.hash) 41 | 42 | def hkdf_expand(config, prk, info, L): 43 | # https://tools.ietf.org/html/rfc5869 44 | # N = ceil(L/HashLen) 45 | # T = T(1) | T(2) | T(3) | ... | T(N) 46 | # OKM = first L octets of T 47 | hash_length = config.hash().digest_size 48 | N = ceil(L / hash_length) 49 | Ts = [bytes(bytearray([]))] 50 | for i in range(N): 51 | Ts.append(hmac.digest( 52 | prk, Ts[i] + info + int(i+1).to_bytes(1, 'big'), config.hash)) 53 | 54 | def concat(a, b): 55 | return a + b 56 | T = reduce(concat, map(lambda c: c, Ts)) 57 | return T[0:L] 58 | 59 | # HKDF-Expand-Label(Secret, Label, Context, Length) = 60 | # HKDF-Expand(Secret, HkdfLabel, Length) 61 | # 62 | # struct { 63 | # uint16 length = Length; 64 | # opaque label<8..255> = "OPAQUE-" + Label; 65 | # opaque context<0..255> = Context; 66 | # } HkdfLabel; 67 | def hkdf_expand_label(config, secret, label, context, length): 68 | def build_label(length, label, context): 69 | return I2OSP(length, 2) + encode_vector_len(_as_bytes("OPAQUE-") + label, 1) + encode_vector_len(context, 1) 70 | hkdf_label = build_label(length, label, context) 71 | return hkdf_expand(config, secret, hkdf_label, length) 72 | 73 | # Derive-Secret(Secret, Label, Transcript) = 74 | # HKDF-Expand-Label(Secret, Label, Hash(Transcript), Nh) 75 | def derive_secret(config, secret, label, transcript_hash): 76 | return hkdf_expand_label(config, secret, label, transcript_hash, config.hash().digest_size) 77 | 78 | # defined in RFC 3447, section 4.1 79 | def I2OSP(val, length): 80 | val = int(val) 81 | if val < 0 or val >= (1 << (8 * length)): 82 | raise ValueError("bad I2OSP call: val=%d length=%d" % (val, length)) 83 | ret = [0] * length 84 | val_ = val 85 | for idx in reversed(range(0, length)): 86 | ret[idx] = val_ & 0xff 87 | val_ = val_ >> 8 88 | ret = struct.pack("=" + "B" * length, *ret) 89 | assert OS2IP(ret, True) == val 90 | return ret 91 | 92 | # defined in RFC 3447, section 4.2 93 | def OS2IP(octets, skip_assert=False): 94 | ret = 0 95 | for octet in struct.unpack("=" + "B" * len(octets), octets): 96 | ret = ret << 8 97 | ret += octet 98 | if not skip_assert: 99 | assert octets == I2OSP(ret, len(octets)) 100 | return ret 101 | 102 | # little-endian version of I2OSP 103 | def I2OSP_le(val, length): 104 | val = int(val) 105 | if val < 0 or val >= (1 << (8 * length)): 106 | raise ValueError("bad I2OSP call: val=%d length=%d" % (val, length)) 107 | ret = [0] * length 108 | val_ = val 109 | for idx in range(0, length): 110 | ret[idx] = val_ & 0xff 111 | val_ = val_ >> 8 112 | ret = struct.pack("=" + "B" * length, *ret) 113 | assert OS2IP_le(ret, True) == val 114 | return ret 115 | 116 | # little-endian version of OS2IP 117 | def OS2IP_le(octets, skip_assert=False): 118 | ret = 0 119 | for octet in reversed(struct.unpack("=" + "B" * len(octets), octets)): 120 | ret = ret << 8 121 | ret += octet 122 | if not skip_assert: 123 | assert octets == I2OSP_le(ret, len(octets)) 124 | return ret 125 | 126 | def encode_vector_len(data, L): 127 | return len(data).to_bytes(L, 'big') + data 128 | 129 | def decode_vector_len(data_bytes, L): 130 | if len(data_bytes) < L: 131 | raise Exception("Insufficient length") 132 | data_len = int.from_bytes(data_bytes[0:L], 'big') 133 | if len(data_bytes) < L+data_len: 134 | raise Exception("Insufficient length") 135 | return data_bytes[L:L+data_len], L+data_len 136 | 137 | def encode_vector(data): 138 | return encode_vector_len(data, 2) 139 | 140 | def decode_vector(data_bytes): 141 | return decode_vector_len(data_bytes, 2) 142 | -------------------------------------------------------------------------------- /poc/opaque_core.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | import hmac 6 | from hash import scrypt 7 | 8 | try: 9 | from sagelib.oprf import SetupOPRFClient, SetupOPRFServer, DeriveKeyPair, MODE_OPRF 10 | from sagelib.opaque_messages import RegistrationRequest, RegistrationResponse, RegistrationUpload, CredentialRequest, CredentialResponse, CleartextCredentials, Envelope, deserialize_envelope 11 | from sagelib.opaque_common import curve25519_clamp, xor, OS2IP, OS2IP_le, _as_bytes, OPAQUE_NONCE_LENGTH 12 | except ImportError as e: 13 | sys.exit("Error loading preprocessed sage files. Try running `make setup && make clean pyfiles`. Full error: " + e) 14 | 15 | OPAQUE_SEED_LENGTH = 32 16 | 17 | class OPAQUECore(object): 18 | def __init__(self, config, rng): 19 | self.config = config 20 | self.rng = rng 21 | 22 | def derive_randomized_password(self, password, response, blind): 23 | oprf_context = SetupOPRFClient(self.config.oprf_suite.identifier) 24 | oprf_output = oprf_context.finalize(password, blind, self.config.oprf_suite.group.deserialize(response.data), None, None, None) 25 | stretched_oprf_output = self.config.ksf.stretch(oprf_output) 26 | return self.config.kdf.extract(_as_bytes(""), oprf_output + stretched_oprf_output) 27 | 28 | def derive_masking_key(self, randomized_password): 29 | Nh = self.config.hash().digest_size 30 | masking_key = self.config.kdf.expand(randomized_password, _as_bytes("MaskingKey"), Nh) 31 | return masking_key 32 | 33 | def create_registration_request(self, password): 34 | oprf_context = SetupOPRFClient(self.config.oprf_suite.identifier) 35 | blind, blinded_element = oprf_context.blind(password, self.rng) 36 | blinded_message = self.config.oprf_suite.group.serialize(blinded_element) 37 | request = RegistrationRequest(blinded_message) 38 | return request, blind 39 | 40 | def create_registration_response(self, request, server_public_key, oprf_seed, credential_identifier): 41 | ikm = self.config.kdf.expand(oprf_seed, credential_identifier + _as_bytes("OprfKey"), OPAQUE_SEED_LENGTH) 42 | (kU, _) = DeriveKeyPair(MODE_OPRF, self.config.oprf_suite.identifier, ikm, _as_bytes("OPAQUE-DeriveKeyPair")) 43 | oprf_context = SetupOPRFServer(self.config.oprf_suite.identifier, kU) 44 | 45 | blinded_element = self.config.oprf_suite.group.deserialize(request.data) 46 | evaluated_element, _, _ = oprf_context.blind_evaluate(blinded_element, None, self.rng) 47 | evaluated_message = self.config.oprf_suite.group.serialize(evaluated_element) 48 | 49 | response = RegistrationResponse(evaluated_message, server_public_key) 50 | return response, kU 51 | 52 | def recover_public_key(self, private_key): 53 | sk = OS2IP(private_key) 54 | if "ristretto" in self.config.group.name or "decaf" in self.config.group.name: 55 | sk = OS2IP_le(private_key) 56 | pk = self.config.group.scalar_mult(sk, self.config.group.generator()) 57 | return self.config.group.serialize(pk) 58 | 59 | def derive_dh_group_key_pair(self, seed): 60 | sk, pk = DeriveKeyPair(MODE_OPRF, self.config.oprf_suite.identifier, seed, _as_bytes("OPAQUE-DeriveDiffieHellmanKeyPair")) 61 | return sk, self.config.group.serialize(pk) 62 | 63 | def derive_diffie_hellman_key_pair(self, seed): 64 | if self.config.group.name == "curve25519": 65 | clamped_seed = curve25519_clamp(seed) 66 | return clamped_seed, self.config.group.serialize(self.config.group.scalar_mult(clamped_seed, self.config.group.generator())) 67 | else: 68 | return self.derive_dh_group_key_pair(seed) 69 | 70 | def create_cleartext_credentials(self, server_public_key_bytes, client_public_key_bytes, server_identity, client_identity): 71 | if server_identity == None: 72 | server_identity = server_public_key_bytes 73 | if client_identity == None: 74 | client_identity = client_public_key_bytes 75 | return CleartextCredentials(server_public_key_bytes, client_identity, server_identity) 76 | 77 | def create_envelope(self, randomized_password, encoded_server_public_key, client_identity, server_identity): 78 | envelope_nonce = self.rng.random_bytes(OPAQUE_NONCE_LENGTH) 79 | Nh = self.config.hash().digest_size 80 | auth_key = self.config.kdf.expand(randomized_password, envelope_nonce + _as_bytes("AuthKey"), Nh) 81 | export_key = self.config.kdf.expand(randomized_password, envelope_nonce + _as_bytes("ExportKey"), Nh) 82 | masking_key = self.derive_masking_key(randomized_password) 83 | 84 | seed = self.config.kdf.expand(randomized_password, envelope_nonce + _as_bytes("PrivateKey"), OPAQUE_SEED_LENGTH) 85 | (_, client_public_key_bytes) = self.derive_diffie_hellman_key_pair(seed) 86 | 87 | cleartext_credentials = self.create_cleartext_credentials(encoded_server_public_key, client_public_key_bytes, server_identity, client_identity) 88 | auth_tag = self.config.mac.mac(auth_key, envelope_nonce + cleartext_credentials.serialize()) 89 | envelope = Envelope(envelope_nonce, auth_tag) 90 | 91 | self.auth_key = auth_key 92 | self.envelope_nonce = envelope.nonce 93 | 94 | return envelope, client_public_key_bytes, masking_key, export_key 95 | 96 | def finalize_request(self, password, blind, response, client_identity=None, server_identity=None): 97 | randomized_password = self.derive_randomized_password(password, response, blind) 98 | envelope, client_public_key, masking_key, export_key = self.create_envelope(randomized_password, response.server_public_key, client_identity, server_identity) 99 | record = RegistrationUpload(client_public_key, masking_key, envelope) 100 | 101 | self.registration_rwdU = randomized_password 102 | self.masking_key = masking_key 103 | 104 | return record, export_key 105 | 106 | def create_credential_request(self, password): 107 | oprf_context = SetupOPRFClient(self.config.oprf_suite.identifier) 108 | blind, blinded_element = oprf_context.blind(password, self.rng) 109 | request = CredentialRequest(self.config.oprf_suite.group.serialize(blinded_element)) 110 | return request, blind 111 | 112 | def create_credential_response(self, request, server_public_key, oprf_seed, envU, credential_identifier, masking_key): 113 | ikm = self.config.kdf.expand(oprf_seed, credential_identifier + _as_bytes("OprfKey"), OPAQUE_SEED_LENGTH) 114 | (kU, _) = DeriveKeyPair(MODE_OPRF, self.config.oprf_suite.identifier, ikm, _as_bytes("OPAQUE-DeriveKeyPair")) 115 | 116 | oprf_context = SetupOPRFServer(self.config.oprf_suite.identifier, kU) 117 | Z, _, _ = oprf_context.blind_evaluate(self.config.oprf_suite.group.deserialize(request.data), None, self.rng) 118 | 119 | masking_nonce = self.rng.random_bytes(OPAQUE_NONCE_LENGTH) 120 | Npk = self.config.Npk 121 | Ne = self.config.Nm + OPAQUE_NONCE_LENGTH 122 | credential_response_pad = self.config.kdf.expand(masking_key, masking_nonce + _as_bytes("CredentialResponsePad"), Npk + Ne) 123 | masked_response = xor(credential_response_pad, server_public_key + envU.serialize()) 124 | 125 | self.masking_nonce = masking_nonce 126 | 127 | response = CredentialResponse(self.config.oprf_suite.group.serialize(Z), masking_nonce, masked_response) 128 | return response 129 | 130 | def recover_keys(self, randomized_password, envelope_nonce): 131 | seed = self.config.kdf.expand(randomized_password, envelope_nonce + _as_bytes("PrivateKey"), OPAQUE_SEED_LENGTH) 132 | (client_private_key, client_public_key_bytes) = self.derive_diffie_hellman_key_pair(seed) 133 | sk_bytes = self.config.group.serialize_scalar(client_private_key) 134 | return sk_bytes, client_public_key_bytes 135 | 136 | def recover_envelope(self, randomized_password, server_public_key, client_identity, server_identity, envelope): 137 | Nh = self.config.hash().digest_size 138 | auth_key = self.config.kdf.expand(randomized_password, envelope.nonce + _as_bytes("AuthKey"), Nh) 139 | export_key = self.config.kdf.expand(randomized_password, envelope.nonce + _as_bytes("ExportKey"), Nh) 140 | 141 | self.credential_auth_key = auth_key 142 | self.credential_export_key = export_key 143 | 144 | client_private_key, client_public_key = self.recover_keys(randomized_password, envelope.nonce) 145 | cleartext_credentials = self.create_cleartext_credentials(server_public_key, client_public_key, server_identity, client_identity) 146 | expected_tag = self.config.mac.mac(auth_key, envelope.nonce + cleartext_credentials.serialize()) 147 | if expected_tag != envelope.auth_tag: 148 | raise Exception("Invalid tag") 149 | 150 | return client_private_key, cleartext_credentials, export_key 151 | 152 | def recover_credentials(self, password, blind, response, client_identity = None, server_identity = None): 153 | randomized_password = self.derive_randomized_password(password, response, blind) 154 | masking_key = self.derive_masking_key(randomized_password) 155 | Npk = self.config.Npk 156 | Ne = self.config.Nm + OPAQUE_NONCE_LENGTH 157 | credential_response_pad = self.config.kdf.expand(masking_key, response.masking_nonce + _as_bytes("CredentialResponsePad"), Npk + Ne) 158 | 159 | data = xor(credential_response_pad, response.masked_response) 160 | server_public_key = data[0:Npk] 161 | envelope, _ = deserialize_envelope(self.config, data[Npk:]) 162 | 163 | self.credential_randomized_password = randomized_password 164 | self.credential_decryption_pad = credential_response_pad 165 | self.credential_masking_key = masking_key 166 | 167 | client_private_key, cleartext_credentials, export_key = self.recover_envelope(randomized_password, server_public_key, client_identity, server_identity, envelope) 168 | 169 | return client_private_key, cleartext_credentials, export_key 170 | 171 | class KeyStretchingFunction(object): 172 | def __init__(self, name, stretch): 173 | self.name = name 174 | self.stretch = stretch 175 | 176 | def identity_stretch(pwd): 177 | return pwd 178 | 179 | class KDF(object): 180 | def __init__(self, name): 181 | self.name = name 182 | 183 | def extract(self, salt, ikm): 184 | raise Exception("Not implemented") 185 | 186 | def expand(self, prk, info, L): 187 | raise Exception("Not implemented") 188 | 189 | class HKDF(KDF): 190 | def __init__(self, fast_hash): 191 | KDF.__init__(self, "HKDF-" + fast_hash().name.upper()) 192 | self.hash = fast_hash 193 | 194 | def extract(self, salt, ikm): 195 | return hmac.digest(salt, ikm, self.hash) 196 | 197 | def expand(self, prk, info, L): 198 | # https://tools.ietf.org/html/rfc5869 199 | # N = ceil(L/HashLen) 200 | # T = T(1) | T(2) | T(3) | ... | T(N) 201 | # OKM = first L octets of T 202 | hash_length = self.hash().digest_size 203 | N = ceil(L / hash_length) 204 | Ts = [bytes(bytearray([]))] 205 | for i in range(N): 206 | Ts.append(hmac.digest( 207 | prk, Ts[i] + info + int(i+1).to_bytes(1, 'big'), self.hash)) 208 | 209 | def concat(a, b): 210 | return a + b 211 | T = reduce(concat, map(lambda c: c, Ts)) 212 | return T[0:L] 213 | 214 | class MAC(object): 215 | def __init__(self, name): 216 | self.name = name 217 | 218 | def mac(self, key, input): 219 | raise Exception("Not implemented") 220 | 221 | class HMAC(MAC): 222 | def __init__(self, fast_hash): 223 | MAC.__init__(self, "HMAC-" + fast_hash().name.upper()) 224 | self.hash = fast_hash 225 | 226 | def output_size(self): 227 | return self.hash().digest_size 228 | 229 | def mac(self, key, input): 230 | return hmac.digest(key, input, self.hash) 231 | 232 | -------------------------------------------------------------------------------- /poc/opaque_drng.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | import random 6 | import hashlib 7 | import struct 8 | 9 | try: 10 | from sagelib.test_drng import TestDRNG 11 | except ImportError as e: 12 | sys.exit("Error loading preprocessed sage files. Try running `make setup && make clean pyfiles`. Full error: " + e) 13 | 14 | # defined in RFC 3447, section 4.1 15 | def I2OSP(val, length): 16 | val = int(val) 17 | if val < 0 or val >= (1 << (8 * length)): 18 | raise ValueError("bad I2OSP call: val=%d length=%d" % (val, length)) 19 | ret = [0] * length 20 | val_ = val 21 | for idx in reversed(range(0, length)): 22 | ret[idx] = val_ & 0xff 23 | val_ = val_ >> 8 24 | ret = struct.pack("=" + "B" * length, *ret) 25 | assert OS2IP(ret, True) == val 26 | return ret 27 | 28 | # defined in RFC 3447, section 4.2 29 | def OS2IP(octets, skip_assert=False): 30 | ret = 0 31 | for octet in struct.unpack("=" + "B" * len(octets), octets): 32 | ret = ret << 8 33 | ret += octet 34 | if not skip_assert: 35 | assert octets == I2OSP(ret, len(octets)) 36 | return ret 37 | 38 | class OPAQUEDRNG(TestDRNG): 39 | def __init__(self, seed): 40 | TestDRNG.__init__(self, seed) 41 | 42 | def random_bytes(self, n): 43 | random.seed(self.seed) 44 | output = I2OSP(random.randrange(2**(8*n)), n) 45 | val = random.randint(0, 2^32) 46 | self.seed = int.from_bytes(hashlib.sha256(int(val % 2^32).to_bytes(4, 'big')).digest(), 'big') 47 | return output -------------------------------------------------------------------------------- /poc/opaque_messages.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | 6 | try: 7 | from sagelib.opaque_common import encode_vector, OPAQUE_NONCE_LENGTH 8 | except ImportError as e: 9 | sys.exit("Error loading preprocessed sage files. Try running `make setup && make clean pyfiles`. Full error: " + e) 10 | 11 | # struct { 12 | # uint8 server_public_key[Npk]; 13 | # uint8 server_identity<1..2^16-1>; 14 | # uint8 client_identity<1..2^16-1>; 15 | # } CleartextCredentials; 16 | class CleartextCredentials(object): 17 | def __init__(self, server_public_key_bytes, client_identity, server_identity): 18 | self.server_public_key_bytes = server_public_key_bytes 19 | self.client_identity = client_identity 20 | self.server_identity = server_identity 21 | 22 | def serialize(self): 23 | return self.server_public_key_bytes + encode_vector(self.server_identity) + encode_vector(self.client_identity) 24 | 25 | class Credentials(object): 26 | def __init__(self, client_private_key, client_public_key, client_identity = None, server_identity = None): 27 | self.client_private_key = client_private_key 28 | self.client_public_key = client_public_key 29 | self.client_identity = client_identity 30 | self.server_identity = server_identity 31 | 32 | # struct { 33 | # opaque nonce[Nn]; 34 | # opaque auth_tag[Nm]; 35 | # } Envelope; 36 | def deserialize_envelope(config, data): 37 | 38 | # TODO(caw): put Nm in the config 39 | Nm = config.hash().digest_size 40 | if len(data) != OPAQUE_NONCE_LENGTH + Nm: 41 | raise Exception("Invalid envelope length") 42 | 43 | nonce = data[:OPAQUE_NONCE_LENGTH] 44 | auth_tag = data[OPAQUE_NONCE_LENGTH:] 45 | 46 | return Envelope(nonce, auth_tag), OPAQUE_NONCE_LENGTH+Nm 47 | 48 | class Envelope(object): 49 | def __init__(self, nonce, auth_tag): 50 | self.nonce = nonce 51 | self.auth_tag = auth_tag 52 | 53 | def serialize(self): 54 | return self.nonce + self.auth_tag 55 | 56 | def __eq__(self, other): 57 | if isinstance(other, Envelope): 58 | serialized = self.serialize() 59 | other_serialized = other.serialize() 60 | return serialized == other_serialized 61 | return False 62 | 63 | class ProtocolMessage(object): 64 | def __init__(self): 65 | pass 66 | 67 | def serialize(self): 68 | raise Exception("Not implemented") 69 | 70 | def __eq__(self, other): 71 | if isinstance(other, ProtocolMessage): 72 | serialized = self.serialize() 73 | other_serialized = other.serialize() 74 | return serialized == other_serialized 75 | return False 76 | 77 | # struct { 78 | # opaque blinded_message[Noe]; 79 | # } RegistrationRequest; 80 | def deserialize_registration_request(config, msg_bytes): 81 | length = config.oprf_suite.group.element_byte_length() 82 | if len(msg_bytes) < length: 83 | raise Exception("Invalid message") 84 | return RegistrationRequest(msg_bytes[0:length]) 85 | 86 | class RegistrationRequest(ProtocolMessage): 87 | def __init__(self, data): 88 | ProtocolMessage.__init__(self) 89 | self.data = data 90 | 91 | def serialize(self): 92 | return self.data 93 | 94 | # struct { 95 | # opaque evaluated_message[Noe]; 96 | # opaque server_public_key[Npk]; 97 | # } RegistrationResponse; 98 | def deserialize_registration_response(config, msg_bytes): 99 | length = config.oprf_suite.group.element_byte_length() 100 | data = msg_bytes[0:length] 101 | server_public_key = msg_bytes[length:] 102 | if len(server_public_key) != config.Npk: 103 | raise Exception("Invalid message: %d %d" % (len(server_public_key), config.Npk)) 104 | 105 | return RegistrationResponse(data, server_public_key) 106 | 107 | class RegistrationResponse(ProtocolMessage): 108 | def __init__(self, data, server_public_key): 109 | ProtocolMessage.__init__(self) 110 | self.data = data 111 | self.server_public_key = server_public_key 112 | 113 | def serialize(self): 114 | return self.data + self.server_public_key 115 | 116 | # struct { 117 | # opaque client_public_key[Npk]; 118 | # opaque masking_key[Nh]; 119 | # Envelope envU; 120 | # } RegistrationUpload; 121 | def deserialize_registration_upload(config, msg_bytes): 122 | if len(msg_bytes) < config.Npk: 123 | raise Exception("Invalid message") 124 | client_public_key = msg_bytes[:config.Npk] 125 | 126 | Nh = config.hash().digest_size 127 | masking_key = msg_bytes[config.Npk:config.Npk+Nh] 128 | 129 | envU, _ = deserialize_envelope(config, msg_bytes[config.Npk+Nh:]) 130 | 131 | return RegistrationUpload(client_public_key, masking_key, envU) 132 | 133 | class RegistrationUpload(ProtocolMessage): 134 | def __init__(self, client_public_key, masking_key, envU): 135 | ProtocolMessage.__init__(self) 136 | self.client_public_key = client_public_key 137 | self.masking_key = masking_key 138 | self.envU = envU 139 | 140 | def serialize(self): 141 | return self.client_public_key + self.masking_key + self.envU.serialize() 142 | 143 | # struct { 144 | # opaque blinded_message[Noe]; 145 | # } CredentialRequest; 146 | def deserialize_credential_request(config, msg_bytes): 147 | length = config.oprf_suite.group.element_byte_length() 148 | if len(msg_bytes) < length: 149 | raise Exception("Invalid message") 150 | return CredentialRequest(msg_bytes[0:length]), length 151 | 152 | class CredentialRequest(ProtocolMessage): 153 | def __init__(self, data): 154 | ProtocolMessage.__init__(self) 155 | self.data = data 156 | 157 | def serialize(self): 158 | return self.data 159 | 160 | # struct { 161 | # opaque evaluated_message[Noe]; 162 | # opaque masking_nonce[32]; 163 | # opaque masked_response[Npk + Ne]; 164 | # } CredentialResponse; 165 | def deserialize_credential_response(config, msg_bytes): 166 | length = config.oprf_suite.group.element_byte_length() 167 | data = msg_bytes[0:length] 168 | masking_nonce = msg_bytes[length:length+OPAQUE_NONCE_LENGTH] 169 | 170 | Nm = config.hash().digest_size 171 | Npk = config.Npk 172 | Ne = Nm + OPAQUE_NONCE_LENGTH 173 | masked_response = msg_bytes[length+OPAQUE_NONCE_LENGTH:length+OPAQUE_NONCE_LENGTH+Npk+Ne] 174 | return CredentialResponse(data, masking_nonce, masked_response), length+OPAQUE_NONCE_LENGTH+Npk+Ne 175 | 176 | class CredentialResponse(ProtocolMessage): 177 | def __init__(self, data, masking_nonce, masked_response): 178 | ProtocolMessage.__init__(self) 179 | self.data = data 180 | self.masking_nonce = masking_nonce 181 | self.masked_response = masked_response 182 | 183 | def serialize(self): 184 | return self.data + self.masking_nonce + self.masked_response 185 | -------------------------------------------------------------------------------- /poc/rfc7748.sage: -------------------------------------------------------------------------------- 1 | from sagelib.string_utils import * 2 | 3 | ########## Definitions from RFC 7748 ################## 4 | 5 | def decodeLittleEndian(b, bits): 6 | b_list = string_or_bytes_to_list(b) 7 | return sum([b_list[i] << 8*i for i in range(floor((bits+7)/8))]) 8 | 9 | def string_or_bytes_to_list(u): 10 | try: 11 | u_list = [ord(b) for b in u] 12 | except: 13 | u_list = [b for b in u] 14 | return u_list 15 | 16 | def decodeUCoordinate(u, bits): 17 | u_list = string_or_bytes_to_list(u) 18 | # Ignore any unused bits. 19 | if bits % 8: 20 | u_list[-1] &= (1<<(bits%8))-1 21 | return decodeLittleEndian(u_list, bits) 22 | 23 | def encodeUCoordinate(u, bits): 24 | num_bytes = floor((bits+7)/8) 25 | result = bytearray(num_bytes) 26 | for i in range(num_bytes): 27 | result[i] = (Integer(u) >> 8*i) & 0xff 28 | return bytes(result) 29 | 30 | def decodeScalar25519(k): 31 | k_list = string_or_bytes_to_list(k) 32 | k_list[0] &= 248 33 | k_list[31] &= 127 34 | k_list[31] |= 64 35 | return decodeLittleEndian(k_list, 255) 36 | 37 | def encodeScalar(u, bits): 38 | num_bytes = floor((bits+7)/8) 39 | result = bytearray(num_bytes) 40 | for i in range(num_bytes): 41 | result[i] = (Integer(u) >> 8*i) & 0xff 42 | return bytes(result) 43 | 44 | ########## Additions ################## 45 | 46 | def decodeScalarForInverse25519(k): 47 | k_list = string_or_bytes_to_list(k) 48 | k_list[0] &= 248 49 | return decodeLittleEndian(k_list, 255) 50 | 51 | def decodeUnclampedScalar(k): 52 | k_list = string_or_bytes_to_list(k) 53 | return decodeLittleEndian(k_list, len(k_list) * 8) 54 | 55 | ########## X25519 ################## 56 | 57 | A_Curve25519 = 486662 58 | q_Curve25519 = 2^255-19 59 | 60 | # all inputs to be given as byte array. 61 | def Inverse_X25519(scalar,basepoint): 62 | OrderPrimeSubgroup = 2^252 + 27742317777372353535851937790883648493 63 | num_bytes_for_field = ceil(log(q_Curve25519,2) / 8) 64 | SF = GF(OrderPrimeSubgroup) 65 | coFactor = 8 66 | scalar_clamped = decodeScalar25519(scalar) 67 | inverse_scalar = 1 / (SF(scalarClamped) * coFactor) 68 | inverse_scalar_int = Integer(inverse_scalar) * coFactor 69 | inverse_scalar = encodeScalar(inverse_scalar_int,num_bytes_for_field * 8) 70 | return X__(basepoint,inverse_scalar, 71 | scalar_decoder=decodeScalarForInverse25519, 72 | warnForPointOnTwist = warnForPointOnTwist, 73 | A = 486662, field_prime = 2^255-19) 74 | 75 | def X25519(scalar, basepoint, warnForPointOnTwist = True, unclamped_basepoint = False): 76 | return X__(scalar, basepoint, 77 | scalar_decoder = decodeScalar25519, 78 | warnForPointOnTwist = warnForPointOnTwist, 79 | A = 486662, field_prime = 2^255-19, unclamped_basepoint = unclamped_basepoint) 80 | 81 | def is_on_curve(basepoint, A = 486662, field_prime = 2^255-19): 82 | F = GF(field_prime) 83 | A = F(A) 84 | num_bits_for_field = ceil(log(float(field_prime),2)) 85 | u = F(decodeUCoordinate(basepoint, num_bits_for_field)) 86 | v2 = u^3 + A*u^2 + u 87 | if not v2.is_square(): 88 | return False 89 | else: 90 | return True # on twist 91 | 92 | def get_nonsquare(F): 93 | """ Argument: F, a field object, e.g., F = GF(2^255 - 19) """ 94 | ctr = F.gen() 95 | while True: 96 | for Z_cand in (F(ctr), F(-ctr)): 97 | # Z must be a non-square in F. 98 | if is_square(Z_cand): 99 | continue 100 | return Z_cand 101 | ctr += 1 102 | 103 | def X__(encoded_scalar, basepoint, scalar_decoder=decodeScalar25519, 104 | warnForPointOnTwist = True, 105 | A = 486662, field_prime = 2^255-19, unclamped_basepoint = False): 106 | """Implements scalar multiplication for X25519.""" 107 | num_bytes_for_field = ceil(log(field_prime,2) / 8) 108 | num_bits_for_field = ceil(log(float(field_prime),2)) 109 | F = GF(Integer(field_prime)) 110 | A = F(A) 111 | nonsquare = get_nonsquare(F) 112 | E = EllipticCurve(F, [0, A , 0, 1 , 0]) 113 | Twist = EllipticCurve(F, [0, A * nonsquare, 0, 1 * nonsquare^2, 0]) 114 | 115 | if unclamped_basepoint: 116 | u = F(decodeUCoordinate(basepoint, num_bits_for_field + 1)) 117 | else: 118 | u = F(decodeUCoordinate(basepoint, num_bits_for_field)) 119 | scalar = scalar_decoder(encoded_scalar) 120 | 121 | d = 1 122 | v2 = u^3 + A*u^2 + u 123 | if not v2.is_square(): 124 | if (warnForPointOnTwist): 125 | print("Input point is on the twist! "), 126 | E = Twist 127 | d = nonsquare 128 | u = d * u 129 | v2 = u^3 + A*u^2 * nonsquare + u * nonsquare^2 130 | v = v2.sqrt() 131 | 132 | point = E(u, v) 133 | (resultPoint_u, resultPoint_v, result_Point_z) = point * scalar 134 | resultCoordinate = resultPoint_u / d 135 | 136 | return encodeUCoordinate(Integer(resultCoordinate),num_bits_for_field) 137 | 138 | class X25519_testCase: 139 | def __init__(self,u_in, s_in, u_out): 140 | self.u_in = u_in 141 | self.s_in = s_in 142 | self.u_out = u_out 143 | 144 | def runTest(self): 145 | us = IntegerToByteArray(self.u_in) 146 | ss = IntegerToByteArray(self.s_in) 147 | r = encodeUCoordinate(self.u_out,256) 148 | u = X25519(ss,us, unclamped_basepoint = True) 149 | if (u != r): 150 | print ("Fail") 151 | print ("Input u :\n0x%032x\n" % self.u_in) 152 | print ("Input s :\n0x%032x\n" % self.s_in) 153 | print ("Correct Result :\n0x%032x\n" % self.u_out) 154 | print ("Actual Result :\n0x%032x\n" % decodeLittleEndian(u,256)) 155 | return False 156 | print ("Pass") 157 | return True 158 | 159 | if __name__ == "__main__": 160 | testCases = [] 161 | 162 | tv = \ 163 | X25519_testCase(0x4c1cabd0a603a9103b35b326ec2466727c5fb124a4c19435db3030586768dbe6,\ 164 | 0xc49a44ba44226a50185afcc10a4c1462dd5e46824b15163b9d7c52f06be346a5,\ 165 | 0x5285a2775507b454f7711c4903cfec324f088df24dea948e90c6e99d3755dac3) 166 | testCases.append(tv) 167 | 168 | tv = X25519_testCase(0x13a415c749d54cfc3e3cc06f10e7db312cae38059d95b7f4d3116878120f21e5,\ 169 | 0xdba18799e16a42cd401eae021641bc1f56a7d959126d25a3c67b4d1d4e9664b,\ 170 | 0x5779ac7a64f7f8e652a19f79685a598bf873b8b45ce4ad7a7d90e87694decb95) 171 | testCases.append(tv) 172 | 173 | tv = X25519_testCase(0,\ 174 | 0xc49a44ba44226a50185afcc10a4c1462dd5e46824b15163b9d7c52f06be346a5,\ 175 | 0) 176 | testCases.append(tv) 177 | 178 | weakp = [] 179 | weakp.append(0) 180 | weakp.append(1) 181 | weakp.append(325606250916557431795983626356110631294008115727848805560023387167927233504) #(which has order 8) 182 | weakp.append(39382357235489614581723060781553021112529911719440698176882885853963445705823) #(which also has order 8) 183 | weakp.append(2^255 - 19 - 1) 184 | weakp.append(2^255 - 19) 185 | weakp.append(2^255 - 19 + 1) 186 | weakp.append(2^255 - 19 + 325606250916557431795983626356110631294008115727848805560023387167927233504) 187 | weakp.append(2^255 - 19 + 39382357235489614581723060781553021112529911719440698176882885853963445705823) 188 | weakp.append(2 * (2^255 - 19) - 1) 189 | weakp.append(2 * (2^255 - 19)) 190 | weakp.append(2 * (2^255 - 19) + 1) 191 | 192 | s_in = 0xff9a44ba44226a50185afcc10a4c1462dd5e46824b15163b9d7c52f06be346af; 193 | for x in weakp: 194 | tv = X25519_testCase (x,s_in,0) 195 | testCases.append(tv) 196 | 197 | for x in testCases: 198 | x.runTest() 199 | 200 | for x in testCases: 201 | x.docOutput() 202 | 203 | input_scalar = 31029842492115040904895560451863089656472772604678260265531221036453811406496 204 | input_coor = 34426434033919594451155107781188821651316167215306631574996226621102155684838 205 | correct_output = 0xc3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552 206 | 207 | own_result = X25519(IntegerToByteArray(input_scalar),IntegerToByteArray(input_coor)) 208 | own_result = bytes(reversed(own_result)) 209 | assert(correct_output == ByteArrayToInteger(own_result, 32)) 210 | -------------------------------------------------------------------------------- /poc/string_utils.sage: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def ByteArrayToInteger(k,numBytes=32): 4 | try: 5 | k_list = [ord(b) for b in k] 6 | except: 7 | k_list = [b for b in k] 8 | 9 | if numBytes < len(k_list): 10 | numBytes = len(k_list) 11 | 12 | return sum((k_list[i] << (8 * i)) for i in range(numBytes)) 13 | 14 | def IntegerToByteArray(k,numBytes = 32): 15 | result = bytearray(numBytes) 16 | for i in range(numBytes): 17 | result[i] = (k >> (8 * i)) & 0xff 18 | return result 19 | -------------------------------------------------------------------------------- /poc/test_drng.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import random 5 | import hashlib 6 | 7 | class TestDRNG(object): 8 | def __init__(self, seed): 9 | self.seed = int.from_bytes(hashlib.sha256(seed).digest(), 'big') 10 | 11 | def randint(self, l, h): 12 | random.seed(self.seed) 13 | val = random.randint(l, h) 14 | self.seed = int.from_bytes(hashlib.sha256(int(val % 2^32).to_bytes(4, 'big')).digest(), 'big') 15 | return val -------------------------------------------------------------------------------- /poc/test_opaque_ake.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | import json 6 | import hashlib 7 | from collections import namedtuple 8 | 9 | try: 10 | from sagelib.oprf import oprf_ciphersuites, ciphersuite_ristretto255_sha512, ciphersuite_p256_sha256 11 | from sagelib.opaque_core import OPAQUECore, HKDF, HMAC, KeyStretchingFunction, identity_stretch 12 | from sagelib.opaque_messages import RegistrationUpload, Envelope, deserialize_envelope, deserialize_registration_request, deserialize_registration_response, \ 13 | deserialize_credential_request, deserialize_credential_response 14 | from sagelib.groups import GroupRistretto255, GroupP256 15 | from sagelib.ake_group import GroupCurve25519 16 | from sagelib.opaque_common import curve25519_clamp, zero_bytes, _as_bytes, to_hex, OPAQUE_NONCE_LENGTH 17 | from sagelib.opaque_ake import OPAQUE3DH, Configuration 18 | from sagelib.opaque_drng import OPAQUEDRNG 19 | except ImportError as e: 20 | sys.exit("Error loading preprocessed sage files. Try running `make setup && make clean pyfiles`. Full error: " + e) 21 | 22 | default_opaque_configuration = Configuration( 23 | oprf_ciphersuites[ciphersuite_ristretto255_sha512], 24 | HKDF(hashlib.sha512), 25 | HMAC(hashlib.sha512), 26 | hashlib.sha512, 27 | KeyStretchingFunction("Identity", identity_stretch), 28 | GroupRistretto255(), 29 | _as_bytes("OPAQUE-POC"), 30 | ) 31 | 32 | def test_core_protocol_serialization(): 33 | client_identity = _as_bytes("Username") 34 | password = _as_bytes("CorrectHorseBatteryStaple") 35 | rng = OPAQUEDRNG("test_core_protocol_serialization".encode('utf-8')) 36 | 37 | config = default_opaque_configuration 38 | group = config.group 39 | server_private_key = group.random_scalar(rng) 40 | server_public_key = group.scalar_mult(server_private_key, group.generator()) 41 | server_public_key_enc = group.serialize(server_public_key) 42 | oprf_seed = rng.random_bytes(config.hash().digest_size) 43 | 44 | core = OPAQUECore(config, rng) 45 | 46 | # Run the registration flow to register credentials 47 | request, metadata = core.create_registration_request(password) 48 | serialized_request = request.serialize() 49 | deserialized_request = deserialize_registration_request( 50 | config, serialized_request) 51 | assert request == deserialized_request 52 | 53 | response, kU = core.create_registration_response(request, server_public_key_enc, oprf_seed, client_identity) 54 | serialized_response = response.serialize() 55 | deserialized_response = deserialize_registration_response( 56 | config, serialized_response) 57 | assert response == deserialized_response 58 | 59 | record, export_key = core.finalize_request(password, metadata, response) 60 | serialized_envU = record.envU.serialize() 61 | deserialized_envU, envU_len = deserialize_envelope(config, serialized_envU) 62 | assert envU_len == len(serialized_envU) 63 | assert record.envU == deserialized_envU 64 | 65 | # Run the authentication flow to recover credentials 66 | cred_request, cred_metadata = core.create_credential_request(password) 67 | serialized_request = cred_request.serialize() 68 | deserialized_request, length = deserialize_credential_request( 69 | config, serialized_request) 70 | assert cred_request == deserialized_request 71 | assert length == len(serialized_request) 72 | 73 | cred_response = core.create_credential_response(cred_request, server_public_key_enc, oprf_seed, record.envU, client_identity, record.masking_key) 74 | serialized_response = cred_response.serialize() 75 | deserialized_response, length = deserialize_credential_response( 76 | config, serialized_response) 77 | assert cred_response == deserialized_response 78 | assert length == len(serialized_response) 79 | 80 | _, _, recovered_export_key = core.recover_credentials(password, cred_metadata, cred_response) 81 | 82 | # Check that recovered credentials match the registered credentials 83 | assert export_key == recovered_export_key 84 | 85 | def test_registration_and_authentication(): 86 | client_identity = _as_bytes("Username") 87 | password = _as_bytes("opaquerulez") 88 | badPwdU = _as_bytes("iloveopaque") 89 | rng = OPAQUEDRNG("test_registration_and_authentication".encode('utf-8')) 90 | 91 | config = default_opaque_configuration 92 | group = config.group 93 | server_private_key = group.random_scalar(rng) 94 | server_public_key = group.scalar_mult(server_private_key, group.generator()) 95 | server_public_key_enc = group.serialize(server_public_key) 96 | oprf_seed = rng.random_bytes(config.hash().digest_size) 97 | 98 | core = OPAQUECore(config, rng) 99 | 100 | request, metadata = core.create_registration_request(password) 101 | response, kU = core.create_registration_response(request, server_public_key_enc, oprf_seed, client_identity) 102 | record, export_key = core.finalize_request(password, metadata, response) 103 | 104 | cred_request, cred_metadata = core.create_credential_request(password) 105 | cred_response = core.create_credential_response(cred_request, server_public_key_enc, oprf_seed, record.envU, client_identity, record.masking_key) 106 | _, _, recovered_export_key = core.recover_credentials(password, cred_metadata, cred_response) 107 | 108 | assert export_key == recovered_export_key 109 | 110 | cred_request, cred_metadata = core.create_credential_request(badPwdU) 111 | cred_response = core.create_credential_response(cred_request, server_public_key_enc, oprf_seed, record.envU, client_identity, record.masking_key) 112 | try: 113 | _, _, recovered_export_key = core.recover_credentials(badPwdU, cred_metadata, cred_response) 114 | assert False 115 | except: 116 | # We expect the MAC authentication tag to fail, so should get here 117 | pass 118 | 119 | # Checks that a the scalar value represented by vector[key] has indeed been clamped 120 | def assert_entry_clamped(vector, key): 121 | if key not in vector: 122 | # No need to do the check if the key doesn't exist 123 | return 124 | 125 | hex_scalar = vector[key] 126 | scalar = _as_bytes(bytes.fromhex(hex_scalar)) 127 | assert scalar == curve25519_clamp(scalar) 128 | 129 | TestVectorParams = namedtuple("TestVectorParams", "is_fake client_identity credential_identifier server_identity password context oprf fast_hash ksf group") 130 | 131 | def run_test_vector(params, seed): 132 | is_fake = params.is_fake 133 | client_identity = params.client_identity 134 | credential_identifier = params.credential_identifier 135 | server_identity = params.server_identity 136 | password = params.password 137 | context = params.context 138 | oprf = params.oprf 139 | fast_hash = params.fast_hash 140 | ksf = params.ksf 141 | group = params.group 142 | core_rng = OPAQUEDRNG(_as_bytes("run_test_vector") + seed) 143 | 144 | server_private_key = group.random_scalar(core_rng) 145 | server_public_key = group.scalar_mult(server_private_key, group.generator()) 146 | server_private_key_bytes = group.serialize_scalar(server_private_key) 147 | server_public_key_bytes = group.serialize(server_public_key) 148 | oprf_seed = core_rng.random_bytes(fast_hash().digest_size) 149 | 150 | kdf = HKDF(fast_hash) 151 | mac = HMAC(fast_hash) 152 | config = Configuration(oprf, kdf, mac, fast_hash, ksf, group, context) 153 | core = OPAQUECore(config, core_rng) 154 | 155 | if not is_fake: 156 | reg_request, metadata = core.create_registration_request(password) 157 | reg_response, kU = core.create_registration_response(reg_request, server_public_key_bytes, oprf_seed, credential_identifier) 158 | record, export_key = core.finalize_request(password, metadata, reg_response, client_identity, server_identity) 159 | client_public_key_bytes = record.client_public_key 160 | client_public_key = group.deserialize(client_public_key_bytes) 161 | else: 162 | fake_client_private_key = group.random_scalar(core_rng) 163 | fake_client_public_key = group.scalar_mult(fake_client_private_key, group.generator()) 164 | fake_client_private_key_bytes = group.serialize_scalar(fake_client_private_key) 165 | fake_client_public_key_bytes = group.serialize(fake_client_public_key) 166 | 167 | fake_masking_key = core_rng.random_bytes(config.Nh) 168 | fake_envU = Envelope(zero_bytes(OPAQUE_NONCE_LENGTH), zero_bytes(config.Nm)) 169 | record = RegistrationUpload(fake_client_public_key_bytes, fake_masking_key, fake_envU) 170 | 171 | client_kex = OPAQUE3DH(config, OPAQUEDRNG(_as_bytes("client") + seed)) 172 | server_kex = OPAQUE3DH(config, OPAQUEDRNG(_as_bytes("server") + seed)) 173 | 174 | ke1 = client_kex.generate_ke1(password) 175 | ke2 = server_kex.generate_ke2(ke1, oprf_seed, credential_identifier, record.envU, record.masking_key, server_identity, server_private_key, server_public_key_bytes, client_identity, fake_client_public_key_bytes if is_fake else client_public_key_bytes) 176 | if is_fake: 177 | try: 178 | ke3 = client_kex.generate_ke3(ke2, client_identity, server_identity) 179 | assert False 180 | except: 181 | # Expected since the MAC was generated using garbage 182 | pass 183 | else: 184 | ke3 = client_kex.generate_ke3(ke2, client_identity, server_identity) 185 | server_session_key = server_kex.auth_server_finish(ke3) 186 | assert server_session_key == client_kex.session_key 187 | 188 | inputs = {} 189 | intermediates = {} 190 | outputs = {} 191 | 192 | # Protocol inputs 193 | if not is_fake: 194 | if client_identity: 195 | inputs["client_identity"] = to_hex(client_identity) 196 | if server_identity: 197 | inputs["server_identity"] = to_hex(server_identity) 198 | inputs["oprf_seed"] = to_hex(oprf_seed) 199 | inputs["credential_identifier"] = to_hex(credential_identifier) 200 | inputs["password"] = to_hex(password) 201 | inputs["server_private_key"] = to_hex(server_private_key_bytes) 202 | inputs["server_public_key"] = to_hex(server_public_key_bytes) 203 | inputs["client_nonce"] = to_hex(client_kex.client_nonce) 204 | inputs["server_nonce"] = to_hex(server_kex.server_nonce) 205 | inputs["client_keyshare_seed"] = to_hex(client_kex.client_keyshare_seed) 206 | inputs["server_keyshare_seed"] = to_hex(server_kex.server_keyshare_seed) 207 | inputs["envelope_nonce"] = to_hex(core.envelope_nonce) 208 | inputs["masking_nonce"] = to_hex(server_kex.masking_nonce) 209 | inputs["blind_registration"] = to_hex(config.oprf_suite.group.serialize_scalar(metadata)) 210 | inputs["blind_login"] = to_hex(config.oprf_suite.group.serialize_scalar(client_kex.cred_metadata)) 211 | 212 | # Intermediate computations 213 | intermediates["client_public_key"] = to_hex(client_public_key_bytes) 214 | intermediates["oprf_key"] = to_hex(config.oprf_suite.group.serialize_scalar(kU)) 215 | intermediates["envelope"] = to_hex(record.envU.serialize()) 216 | intermediates["randomized_password"] = to_hex(client_kex.core.credential_randomized_password) 217 | intermediates["masking_key"] = to_hex(client_kex.core.credential_masking_key) 218 | intermediates["auth_key"] = to_hex(client_kex.core.credential_auth_key) 219 | intermediates["server_mac_key"] = to_hex(client_kex.server_mac_key) 220 | intermediates["client_mac_key"] = to_hex(client_kex.client_mac_key) 221 | intermediates["handshake_secret"] = to_hex(client_kex.handshake_secret) 222 | 223 | # Protocol outputs 224 | outputs["registration_request"] = to_hex(reg_request.serialize()) 225 | outputs["registration_response"] = to_hex(reg_response.serialize()) 226 | outputs["registration_upload"] = to_hex(record.serialize()) 227 | outputs["KE1"] = to_hex(ke1) 228 | outputs["KE2"] = to_hex(ke2) 229 | outputs["KE3"] = to_hex(ke3) 230 | outputs["session_key"] = to_hex(server_session_key) 231 | outputs["export_key"] = to_hex(export_key) 232 | else: 233 | # client_public_key, server_public_key, ke1, record.envU, record.masking_key, server_identity, server_private_key, server_public_key, client_identity, client_public_key 234 | if client_identity: 235 | inputs["client_identity"] = to_hex(client_identity) 236 | if server_identity: 237 | inputs["server_identity"] = to_hex(server_identity) 238 | inputs["oprf_seed"] = to_hex(oprf_seed) 239 | inputs["credential_identifier"] = to_hex(credential_identifier) 240 | inputs["client_private_key"] = to_hex(fake_client_private_key_bytes) 241 | inputs["client_public_key"] = to_hex(fake_client_public_key_bytes) 242 | inputs["server_private_key"] = to_hex(server_private_key_bytes) 243 | inputs["server_public_key"] = to_hex(server_public_key_bytes) 244 | inputs["server_nonce"] = to_hex(server_kex.server_nonce) 245 | inputs["client_keyshare_seed"] = to_hex(client_kex.client_keyshare_seed) 246 | inputs["server_keyshare_seed"] = to_hex(server_kex.server_keyshare_seed) 247 | inputs["masking_key"] = to_hex(fake_masking_key) 248 | inputs["masking_nonce"] = to_hex(server_kex.masking_nonce) 249 | inputs["KE1"] = to_hex(ke1) 250 | 251 | # Protocol outputs 252 | outputs["KE2"] = to_hex(ke2) 253 | 254 | vector = {} 255 | vector["config"] = client_kex.json() 256 | vector["config"]["Fake"] = str(is_fake) 257 | vector["inputs"] = inputs 258 | vector["intermediates"] = intermediates 259 | vector["outputs"] = outputs 260 | 261 | return vector 262 | 263 | def test_3DH(): 264 | client_identity = _as_bytes("alice") 265 | credential_identifier = _as_bytes("1234") 266 | server_identity = _as_bytes("bob") 267 | password = _as_bytes("CorrectHorseBatteryStaple") 268 | context = _as_bytes("OPAQUE-POC") 269 | 270 | # Configurations specified here: 271 | # https://cfrg.github.io/draft-irtf-cfrg-opaque/draft-irtf-cfrg-opaque.html#name-configurations 272 | configs = [ 273 | (oprf_ciphersuites[ciphersuite_ristretto255_sha512], hashlib.sha512, KeyStretchingFunction("Identity", identity_stretch), GroupRistretto255()), 274 | (oprf_ciphersuites[ciphersuite_ristretto255_sha512], hashlib.sha512, KeyStretchingFunction("Identity", identity_stretch), GroupCurve25519()), 275 | (oprf_ciphersuites[ciphersuite_p256_sha256], hashlib.sha256, KeyStretchingFunction("Identity", identity_stretch), GroupP256()), 276 | ] 277 | 278 | vectors = [] 279 | for (oprf, fast_hash, ksf, group) in configs: 280 | for (client_identity, server_identity) in [(None, None), (client_identity, server_identity)]: 281 | params = TestVectorParams(False, client_identity, credential_identifier, server_identity, password, context, oprf, fast_hash, ksf, group) 282 | vector = run_test_vector(params, _as_bytes("real test vector seed")) 283 | vectors.append(vector) 284 | 285 | for (oprf, fast_hash, ksf, group) in configs: 286 | fake_params = TestVectorParams(True, client_identity, credential_identifier, server_identity, password, context, oprf, fast_hash, ksf, group) 287 | vector = run_test_vector(fake_params, _as_bytes("fake test vector seed")) 288 | vectors.append(vector) 289 | 290 | # Ensure that curve25519 private keys are all clamped 291 | for vector in vectors: 292 | if vector["config"]["Group"] == "curve25519": 293 | assert_entry_clamped(vector["inputs"], "client_private_key") 294 | assert_entry_clamped(vector["inputs"], "server_private_key") 295 | assert_entry_clamped(vector["inputs"], "client_private_keyshare") 296 | assert_entry_clamped(vector["inputs"], "server_private_keyshare") 297 | 298 | return json.dumps(vectors, sort_keys=True, indent=2) 299 | 300 | def main(path="vectors"): 301 | test_core_protocol_serialization() 302 | test_registration_and_authentication() 303 | 304 | formatted_vectors = test_3DH() 305 | with open(os.path.join(path, "vectors.json"), "w") as fh: 306 | fh.write(formatted_vectors) 307 | 308 | if __name__ == "__main__": 309 | main() 310 | -------------------------------------------------------------------------------- /poc/vectors/formatted.txt: -------------------------------------------------------------------------------- 1 | ## Real Test Vectors {#real-vectors} 2 | 3 | ### OPAQUE-3DH Real Test Vector 1 4 | 5 | #### Configuration 6 | 7 | ~~~ 8 | OPRF: ristretto255-SHA512 9 | Hash: SHA512 10 | KSF: Identity 11 | KDF: HKDF-SHA512 12 | MAC: HMAC-SHA512 13 | Group: ristretto255 14 | Context: 4f50415155452d504f43 15 | Nh: 64 16 | Npk: 32 17 | Nsk: 32 18 | Nm: 64 19 | Nx: 64 20 | Nok: 32 21 | ~~~ 22 | 23 | #### Input Values 24 | 25 | ~~~ 26 | oprf_seed: f433d0227b0b9dd54f7c4422b600e764e47fb503f1f9a0f0a47c6606b0 27 | 54a7fdc65347f1a08f277e22358bbabe26f823fca82c7848e9a75661f4ec5d5c1989e 28 | f 29 | credential_identifier: 31323334 30 | password: 436f7272656374486f72736542617474657279537461706c65 31 | envelope_nonce: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d2 32 | 3ba7a38dfec 33 | masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80 34 | f612fdfc6d 35 | server_private_key: 47451a85372f8b3537e249d7b54188091fb18edde78094b43 36 | e2ba42b5eb89f0d 37 | server_public_key: b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a 38 | 382c9b79df1a78 39 | server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e 40 | 138e3d4a1 41 | client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38 42 | 0cae6a6cc 43 | client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf 44 | 1091933541936304b 45 | server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a 46 | 1e85ff80da12f982f 47 | blind_registration: 76cfbfe758db884bebb33582331ba9f159720ca8784a2a070 48 | a265d9c2d6abe01 49 | blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4 50 | b0790308 51 | ~~~ 52 | 53 | #### Intermediate Values 54 | 55 | ~~~ 56 | client_public_key: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbac 57 | cafb57ac5c3675 58 | auth_key: 6cd32316f18d72a9a927a83199fa030663a38ce0c11fbaef82aa9003773 59 | 0494fc555c4d49506284516edd1628c27965b7555a4ebfed2223199f6c67966dde822 60 | randomized_password: aac48c25ab036e30750839d31d6e73007344cb1155289fb7 61 | d329beb932e9adeea73d5d5c22a0ce1952f8aba6d66007615cd1698d4ac85ef1fcf15 62 | 0031d1435d9 63 | envelope: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a3 64 | 8dfec634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b 65 | 8d9e9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5 66 | handshake_secret: 81263cb85a0cfa12450f0f388de4e92291ec4c7c7a0878b6245 67 | 50ff528726332f1298fc6cc822a432c89504347c7a2ccd70316ae3da6a15e0399e6db 68 | 3f7c1b12 69 | server_mac_key: 0d36b26cfe38f51f804f0a9361818f32ee1ce2a4e5578653b5271 70 | 84af058d3b2d8075c296fd84d24677913d1baa109290cd81a13ed383f9091a3804e65 71 | 298dfc 72 | client_mac_key: 91750adbac54a5e8e53b4c233cc8d369fe83b0de1b6a3cd85575e 73 | eb0bb01a6a90a086a2cf5fe75fff2a9379c30ba9049510a33b5b0b1444a88800fc3ee 74 | e2260d 75 | oprf_key: 5d4c6a8b7c7138182afb4345d1fae6a9f18a1744afbcc3854f8f5a2b4b4 76 | c6d05 77 | ~~~ 78 | 79 | #### Output Values 80 | 81 | ~~~ 82 | registration_request: 5059ff249eb1551b7ce4991f3336205bde44a105a032e74 83 | 7d21bf382e75f7a71 84 | registration_response: 7408a268083e03abc7097fc05b587834539065e86fb0c7 85 | b6342fcf5e01e5b019b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a3 86 | 82c9b79df1a78 87 | registration_upload: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccb 88 | accafb57ac5c36751ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e 89 | 66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa8 90 | 2785c5ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfe 91 | c634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b8d9e 92 | 9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5 93 | KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d 94 | da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc6e29b 95 | ee50701498605b2c085d7b241ca15ba5c32027dd21ba420b94ce60da326 96 | KE2: 7e308140890bcde30cbcea28b01ea1ecfbd077cff62c4def8efa075aabcbb471 97 | 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6dd6ec6 98 | 0bcdb26dc455ddf3e718f1020490c192d70dfc7e403981179d8073d1146a4f9aa1ced 99 | 4e4cd984c657eb3b54ced3848326f70331953d91b02535af44d9fedc80188ca46743c 100 | 52786e0382f95ad85c08f6afcd1ccfbff95e2bdeb015b166c6b20b92f832cc6df01e0 101 | b86a7efd92c1c804ff865781fa93f2f20b446c8371b671cd9960ecef2fe0d0f749498 102 | 6fa3d8b2bb01963537e60efb13981e138e3d4a1c4f62198a9d6fa9170c42c3c71f197 103 | 1b29eb1d5d0bd733e40816c91f7912cc4a660c48dae03e57aaa38f3d0cffcfc21852e 104 | bc8b405d15bd6744945ba1a93438a162b6111699d98a16bb55b7bdddfe0fc5608b23d 105 | a246e7bd73b47369169c5c90 106 | KE3: 4455df4f810ac31a6748835888564b536e6da5d9944dfea9e34defb9575fe5e2 107 | 661ef61d2ae3929bcf57e53d464113d364365eb7d1a57b629707ca48da18e442 108 | export_key: 1ef15b4fa99e8a852412450ab78713aad30d21fa6966c9b8c9fb3262a 109 | 970dc62950d4dd4ed62598229b1b72794fc0335199d9f7fcc6eaedde92cc04870e63f 110 | 16 111 | session_key: 42afde6f5aca0cfa5c163763fbad55e73a41db6b41bc87b8e7b62214 112 | a8eedc6731fa3cb857d657ab9b3764b89a84e91ebcb4785166fbb02cedfcbdfda215b 113 | 96f 114 | ~~~ 115 | 116 | ### OPAQUE-3DH Real Test Vector 2 117 | 118 | #### Configuration 119 | 120 | ~~~ 121 | OPRF: ristretto255-SHA512 122 | Hash: SHA512 123 | KSF: Identity 124 | KDF: HKDF-SHA512 125 | MAC: HMAC-SHA512 126 | Group: ristretto255 127 | Context: 4f50415155452d504f43 128 | Nh: 64 129 | Npk: 32 130 | Nsk: 32 131 | Nm: 64 132 | Nx: 64 133 | Nok: 32 134 | ~~~ 135 | 136 | #### Input Values 137 | 138 | ~~~ 139 | client_identity: 616c696365 140 | server_identity: 626f62 141 | oprf_seed: f433d0227b0b9dd54f7c4422b600e764e47fb503f1f9a0f0a47c6606b0 142 | 54a7fdc65347f1a08f277e22358bbabe26f823fca82c7848e9a75661f4ec5d5c1989e 143 | f 144 | credential_identifier: 31323334 145 | password: 436f7272656374486f72736542617474657279537461706c65 146 | envelope_nonce: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d2 147 | 3ba7a38dfec 148 | masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80 149 | f612fdfc6d 150 | server_private_key: 47451a85372f8b3537e249d7b54188091fb18edde78094b43 151 | e2ba42b5eb89f0d 152 | server_public_key: b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a 153 | 382c9b79df1a78 154 | server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e 155 | 138e3d4a1 156 | client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38 157 | 0cae6a6cc 158 | client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf 159 | 1091933541936304b 160 | server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a 161 | 1e85ff80da12f982f 162 | blind_registration: 76cfbfe758db884bebb33582331ba9f159720ca8784a2a070 163 | a265d9c2d6abe01 164 | blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4 165 | b0790308 166 | ~~~ 167 | 168 | #### Intermediate Values 169 | 170 | ~~~ 171 | client_public_key: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbac 172 | cafb57ac5c3675 173 | auth_key: 6cd32316f18d72a9a927a83199fa030663a38ce0c11fbaef82aa9003773 174 | 0494fc555c4d49506284516edd1628c27965b7555a4ebfed2223199f6c67966dde822 175 | randomized_password: aac48c25ab036e30750839d31d6e73007344cb1155289fb7 176 | d329beb932e9adeea73d5d5c22a0ce1952f8aba6d66007615cd1698d4ac85ef1fcf15 177 | 0031d1435d9 178 | envelope: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a3 179 | 8dfec1ac902dc5589e9a5f0de56ad685ea8486210ef41449cd4d8712828913c5d2b68 180 | 0b2b3af4a26c765cff329bfb66d38ecf1d6cfa9e7a73c222c6efe0d9520f7d7c 181 | handshake_secret: 5e723bed1e5276de2503419eba9da61ead573109c4012268323 182 | 98c7e08155b885bfe7bc93451f9d887a0c1d0c19233e40a8e47b347a9ac3907f94032 183 | a4cff64f 184 | server_mac_key: dad66bb9251073d17a13f8e5500f36e5998e3cde520ca0738e708 185 | 5af62fd97812eb79a745c94d0bf8a6ac17f980cf435504cf64041eeb6bb237796d2c7 186 | f81e9a 187 | client_mac_key: f816fe2914f7c5b29852385615d7c7f31ac122adf202d7ccd4976 188 | 06d7aabd48930323d1d02b1cc9ecd456c4de6f46c7950becb18bffd921dd5876381b5 189 | 486ffe 190 | oprf_key: 5d4c6a8b7c7138182afb4345d1fae6a9f18a1744afbcc3854f8f5a2b4b4 191 | c6d05 192 | ~~~ 193 | 194 | #### Output Values 195 | 196 | ~~~ 197 | registration_request: 5059ff249eb1551b7ce4991f3336205bde44a105a032e74 198 | 7d21bf382e75f7a71 199 | registration_response: 7408a268083e03abc7097fc05b587834539065e86fb0c7 200 | b6342fcf5e01e5b019b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a3 201 | 82c9b79df1a78 202 | registration_upload: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccb 203 | accafb57ac5c36751ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e 204 | 66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa8 205 | 2785c5ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfe 206 | c1ac902dc5589e9a5f0de56ad685ea8486210ef41449cd4d8712828913c5d2b680b2b 207 | 3af4a26c765cff329bfb66d38ecf1d6cfa9e7a73c222c6efe0d9520f7d7c 208 | KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d 209 | da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc6e29b 210 | ee50701498605b2c085d7b241ca15ba5c32027dd21ba420b94ce60da326 211 | KE2: 7e308140890bcde30cbcea28b01ea1ecfbd077cff62c4def8efa075aabcbb471 212 | 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6dd6ec6 213 | 0bcdb26dc455ddf3e718f1020490c192d70dfc7e403981179d8073d1146a4f9aa1ced 214 | 4e4cd984c657eb3b54ced3848326f70331953d91b02535af44d9fea502150b67fe367 215 | 95dd8914f164e49f81c7688a38928372134b7dccd50e09f8fed9518b7b2f94835b3c4 216 | fe4c8475e7513f20eb97ff0568a39caee3fd6251876f71cd9960ecef2fe0d0f749498 217 | 6fa3d8b2bb01963537e60efb13981e138e3d4a1c4f62198a9d6fa9170c42c3c71f197 218 | 1b29eb1d5d0bd733e40816c91f7912cc4a292371e7809a9031743e943fb3b56f51de9 219 | 03552fc91fba4e7419029951c3970b2e2f0a9dea218d22e9e4e0000855bb6421aa361 220 | 0d6fc0f4033a6517030d4341 221 | KE3: 7a026de1d6126905736c3f6d92463a08d209833eb793e46d0f7f15b3e0f62c76 222 | 43763c02bbc6b8d3d15b63250cae98171e9260f1ffa789750f534ac11a0176d5 223 | export_key: 1ef15b4fa99e8a852412450ab78713aad30d21fa6966c9b8c9fb3262a 224 | 970dc62950d4dd4ed62598229b1b72794fc0335199d9f7fcc6eaedde92cc04870e63f 225 | 16 226 | session_key: ae7951123ab5befc27e62e63f52cf472d6236cb386c968cc47b7e34f 227 | 866aa4bc7638356a73cfce92becf39d6a7d32a1861f12130e824241fe6cab34fbd471 228 | a57 229 | ~~~ 230 | 231 | ### OPAQUE-3DH Real Test Vector 3 232 | 233 | #### Configuration 234 | 235 | ~~~ 236 | OPRF: ristretto255-SHA512 237 | Hash: SHA512 238 | KSF: Identity 239 | KDF: HKDF-SHA512 240 | MAC: HMAC-SHA512 241 | Group: curve25519 242 | Context: 4f50415155452d504f43 243 | Nh: 64 244 | Npk: 32 245 | Nsk: 32 246 | Nm: 64 247 | Nx: 64 248 | Nok: 32 249 | ~~~ 250 | 251 | #### Input Values 252 | 253 | ~~~ 254 | oprf_seed: a78342ab84d3d30f08d5a9630c79bf311c31ed7f85d9d4959bf492ec67 255 | a0eec8a67dfbf4497248eebd49e878aab173e5e4ff76354288fdd53e949a5f7c9f7f1 256 | b 257 | credential_identifier: 31323334 258 | password: 436f7272656374486f72736542617474657279537461706c65 259 | envelope_nonce: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cc 260 | a9bf44d6e0b 261 | masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80 262 | f612fdfc6d 263 | server_private_key: c06139381df63bfc91c850db0b9cfbec7a62e86d80040a41a 264 | a7725bf0e79d564 265 | server_public_key: a41e28269b4e97a66468cc00c5a57753e192e1527669897706 266 | 88aa90486ef031 267 | server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e 268 | 138e3d4a1 269 | client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38 270 | 0cae6a6cc 271 | client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf 272 | 1091933541936304b 273 | server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a 274 | 1e85ff80da12f982f 275 | blind_registration: c575731ffe1cb0ca5ba63b42c4699767b8b9ab78ba39316ee 276 | 04baddb2034a70a 277 | blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4 278 | b0790308 279 | ~~~ 280 | 281 | #### Intermediate Values 282 | 283 | ~~~ 284 | client_public_key: 0936ea94ab030ec332e29050d266c520e916731a052d05ced7 285 | e0cfe751142b48 286 | auth_key: 7e880ab484f750e80e6f839d975aff476070ce65066d85ea62523d1d576 287 | 4739d91307fac47186a4ab935e6a5c7f70cb47faa9473311947502c022cc67ae9440c 288 | randomized_password: 3a602c295a9c323d9362fe286f104567ed6862b25dbe30fa 289 | da844f19e41cf40047424b7118e15dc2c1a815a70fea5c8de6c30aa61440cd4b4b5e8 290 | f3963fbb2e1 291 | envelope: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44 292 | d6e0b20c1e81fef28e92e897ca8287d49a55075b47c3988ff0fff367d79a3e350ccac 293 | 150b4a3ff48b4770c8e84e437b3d4e68d2b95833f7788f7eb93fa6a8afb85ecb 294 | handshake_secret: 178c8c15e025252380c3edb1c6ad8ac52573b38d536099e2f86 295 | 5786f5e31c642608550c0c6f281c37ce259667dd72768af31630e0eb36f1096a2e642 296 | 1c2aa163 297 | server_mac_key: f3c6a8e069c54bb0d8905139f723c9e22f5c662dc08848243a665 298 | 4c8223800019b9823523d84da2ef67ca1c14277630aace464c113be8a0a658c39e181 299 | a8bb71 300 | client_mac_key: b1ee7ce52dbd0ab72872924ff11596cb196bbabfc319e74aca78a 301 | de54a0f74dd15dcf5621f6d2e79161b0c9b701381d494836dedbb86e584a65b34267a 302 | 370e01 303 | oprf_key: 62ef7f7d9506a14600c34f642aaf6ef8019cc82a6755db4fded5248ea14 304 | 6030a 305 | ~~~ 306 | 307 | #### Output Values 308 | 309 | ~~~ 310 | registration_request: 26f3dbfd76b8e5f85b4da604f42889a7d4b1bc919f65538 311 | 1a67de02c59fd5436 312 | registration_response: 506e8f1b89c098fb89b5b6210a05f7898cafdaea221761 313 | e8d5272fc39e0f9f08a41e28269b4e97a66468cc00c5a57753e192e15276698977068 314 | 8aa90486ef031 315 | registration_upload: 0936ea94ab030ec332e29050d266c520e916731a052d05ce 316 | d7e0cfe751142b486d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e 317 | 7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9e 318 | f53c3540d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0 319 | b20c1e81fef28e92e897ca8287d49a55075b47c3988ff0fff367d79a3e350ccac150b 320 | 4a3ff48b4770c8e84e437b3d4e68d2b95833f7788f7eb93fa6a8afb85ecb 321 | KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d 322 | da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc10a83 323 | b9117d3798cb2957fbdb0268a0d63dbf9d66bde5c00c78affd80026c911 324 | KE2: 9a0e5a1514f62e005ea098b0d8cf6750e358c4389e6add1c52aed9500fa19d00 325 | 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d22cc3 326 | 1127d6f0096755be3c3d2dd6287795c317aeea10c9485bf4f419a786642c19a8f151c 327 | eb5e8767d175248c62c017de94057398d28bf0ed00d1b50ee4f812fd9afddf98af8cd 328 | 58067ca43b0633b6cadd0e9d987f89623fed4d3583bdf6910c425600e90dab3c6b351 329 | 3188a465461a67f6bbc47aeba808f7f7e2c6d66f5c3271cd9960ecef2fe0d0f749498 330 | 6fa3d8b2bb01963537e60efb13981e138e3d4a141f55f0bef355cfb34ccd468fdacad 331 | 75865ee7efef95f4cb6c25d477f720502676f06a3b806da262139bf3fa76a1090b94d 332 | ac78bc3bc6f8747d5b35acf94eff3ec2ebe7d49b8cf16be64120b279fe92664e47be5 333 | da7e60f08f12e91192652f79 334 | KE3: 550e923829a544496d8316c490da2b979b78c730dd75be3a17f237a26432c19f 335 | bba54b6a0467b1c22ecbd6794bc5fa5b04215ba1ef974c6b090baa42c5bb984f 336 | export_key: 9dec51d6d0f6ce7e4345f10961053713b07310cc2e45872f57bbd2fe5 337 | 070fdf0fb5b77c7ddaa2f3dc5c35132df7417ad7fefe0f690ad266e5a54a21d045c9c 338 | 38 339 | session_key: fd2fdd07c1bcc88e81c1b1d1de5ad62dfdef1c0b8209ff9d671e1fac 340 | 55ce9c34d381c1fb2703ff53a797f77daccbe33047ccc167b8105171e10ec962eea20 341 | 3aa 342 | ~~~ 343 | 344 | ### OPAQUE-3DH Real Test Vector 4 345 | 346 | #### Configuration 347 | 348 | ~~~ 349 | OPRF: ristretto255-SHA512 350 | Hash: SHA512 351 | KSF: Identity 352 | KDF: HKDF-SHA512 353 | MAC: HMAC-SHA512 354 | Group: curve25519 355 | Context: 4f50415155452d504f43 356 | Nh: 64 357 | Npk: 32 358 | Nsk: 32 359 | Nm: 64 360 | Nx: 64 361 | Nok: 32 362 | ~~~ 363 | 364 | #### Input Values 365 | 366 | ~~~ 367 | client_identity: 616c696365 368 | server_identity: 626f62 369 | oprf_seed: a78342ab84d3d30f08d5a9630c79bf311c31ed7f85d9d4959bf492ec67 370 | a0eec8a67dfbf4497248eebd49e878aab173e5e4ff76354288fdd53e949a5f7c9f7f1 371 | b 372 | credential_identifier: 31323334 373 | password: 436f7272656374486f72736542617474657279537461706c65 374 | envelope_nonce: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cc 375 | a9bf44d6e0b 376 | masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80 377 | f612fdfc6d 378 | server_private_key: c06139381df63bfc91c850db0b9cfbec7a62e86d80040a41a 379 | a7725bf0e79d564 380 | server_public_key: a41e28269b4e97a66468cc00c5a57753e192e1527669897706 381 | 88aa90486ef031 382 | server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e 383 | 138e3d4a1 384 | client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38 385 | 0cae6a6cc 386 | client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf 387 | 1091933541936304b 388 | server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a 389 | 1e85ff80da12f982f 390 | blind_registration: c575731ffe1cb0ca5ba63b42c4699767b8b9ab78ba39316ee 391 | 04baddb2034a70a 392 | blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4 393 | b0790308 394 | ~~~ 395 | 396 | #### Intermediate Values 397 | 398 | ~~~ 399 | client_public_key: 0936ea94ab030ec332e29050d266c520e916731a052d05ced7 400 | e0cfe751142b48 401 | auth_key: 7e880ab484f750e80e6f839d975aff476070ce65066d85ea62523d1d576 402 | 4739d91307fac47186a4ab935e6a5c7f70cb47faa9473311947502c022cc67ae9440c 403 | randomized_password: 3a602c295a9c323d9362fe286f104567ed6862b25dbe30fa 404 | da844f19e41cf40047424b7118e15dc2c1a815a70fea5c8de6c30aa61440cd4b4b5e8 405 | f3963fbb2e1 406 | envelope: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44 407 | d6e0bb4c0eab6143959a650c5f6b32acf162b1fbe95bb36c5c4f99df53865c4d3537d 408 | 69061d80522d772cd0efdbe91f817f6bf7259a56e20b4eb9cbe9443702f4b759 409 | handshake_secret: 13e7dc6afa5334b9dfffe26bee3caf744ef4add176caee464cd 410 | eb3d37303b90de35a8bf095df84471ac77d705f12fe232f1571de1d6a001d3e808998 411 | 73a142dc 412 | server_mac_key: a58135acfb2bde92d506cf59119729a6404ad94eba294e4b52a63 413 | baf58cfe03f21bcf735222c7f2c27a60bd958be7f6aed50dc03a78f64e7ae4ac1ff07 414 | 1b95aa 415 | client_mac_key: 1e1a8ba156aadc4a302f707d2193c9dab477b355f430d450dd407 416 | ce40dc75613f76ec33dec494f8a6bfdcf951eb060dac33e6572c693954fe92e33730c 417 | 9ab0a2 418 | oprf_key: 62ef7f7d9506a14600c34f642aaf6ef8019cc82a6755db4fded5248ea14 419 | 6030a 420 | ~~~ 421 | 422 | #### Output Values 423 | 424 | ~~~ 425 | registration_request: 26f3dbfd76b8e5f85b4da604f42889a7d4b1bc919f65538 426 | 1a67de02c59fd5436 427 | registration_response: 506e8f1b89c098fb89b5b6210a05f7898cafdaea221761 428 | e8d5272fc39e0f9f08a41e28269b4e97a66468cc00c5a57753e192e15276698977068 429 | 8aa90486ef031 430 | registration_upload: 0936ea94ab030ec332e29050d266c520e916731a052d05ce 431 | d7e0cfe751142b486d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e 432 | 7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9e 433 | f53c3540d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0 434 | bb4c0eab6143959a650c5f6b32acf162b1fbe95bb36c5c4f99df53865c4d3537d6906 435 | 1d80522d772cd0efdbe91f817f6bf7259a56e20b4eb9cbe9443702f4b759 436 | KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d 437 | da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc10a83 438 | b9117d3798cb2957fbdb0268a0d63dbf9d66bde5c00c78affd80026c911 439 | KE2: 9a0e5a1514f62e005ea098b0d8cf6750e358c4389e6add1c52aed9500fa19d00 440 | 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d22cc3 441 | 1127d6f0096755be3c3d2dd6287795c317aeea10c9485bf4f419a786642c19a8f151c 442 | eb5e8767d175248c62c017de94057398d28bf0ed00d1b50ee4f812699bff7663be3c5 443 | d59de94d8e7e58817c7da005b39c25d25555c929e1c5cf6c1b82837b1367c839aab56 444 | a422c0d97719426a79a16f9869cf852100597b23b5a071cd9960ecef2fe0d0f749498 445 | 6fa3d8b2bb01963537e60efb13981e138e3d4a141f55f0bef355cfb34ccd468fdacad 446 | 75865ee7efef95f4cb6c25d477f72050267cc22c87edbf3ecaca64cb33bc60dc3bfc5 447 | 51e365f0d46a7fed0e09d96f9afbb48868f5bb3c3e05a86ed8c9476fc22c58306c5a2 448 | 91be34388e09548ba9d70f39 449 | KE3: d16344e791c3f18594d22ba068984fa18ec1e9bead662b75f66826ffd627932f 450 | cd1ec40cd01dcf5f63f4055ebe45c7717a57a833aad360256cf1e1c20c0eae1c 451 | export_key: 9dec51d6d0f6ce7e4345f10961053713b07310cc2e45872f57bbd2fe5 452 | 070fdf0fb5b77c7ddaa2f3dc5c35132df7417ad7fefe0f690ad266e5a54a21d045c9c 453 | 38 454 | session_key: f6116d3aa0e4089a179713bad4d98ed5cb57e5443cae8d36ef78996f 455 | a60f3dc6e9fcdd63c001596b06dbc1285d80211035cc0e485506b3f7a650cbf78c5bf 456 | fc9 457 | ~~~ 458 | 459 | ### OPAQUE-3DH Real Test Vector 5 460 | 461 | #### Configuration 462 | 463 | ~~~ 464 | OPRF: P256-SHA256 465 | Hash: SHA256 466 | KSF: Identity 467 | KDF: HKDF-SHA256 468 | MAC: HMAC-SHA256 469 | Group: P256_XMD:SHA-256_SSWU_RO_ 470 | Context: 4f50415155452d504f43 471 | Nh: 32 472 | Npk: 33 473 | Nsk: 32 474 | Nm: 32 475 | Nx: 32 476 | Nok: 32 477 | ~~~ 478 | 479 | #### Input Values 480 | 481 | ~~~ 482 | oprf_seed: 62f60b286d20ce4fd1d64809b0021dad6ed5d52a2c8cf27ae6582543a0 483 | a8dce2 484 | credential_identifier: 31323334 485 | password: 436f7272656374486f72736542617474657279537461706c65 486 | envelope_nonce: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebd 487 | cf65670e51f 488 | masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80 489 | f612fdfc6d 490 | server_private_key: c36139381df63bfc91c850db0b9cfbec7a62e86d80040a41a 491 | a7725bf0e79d5e5 492 | server_public_key: 035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9 493 | f2092d6067784874 494 | server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e 495 | 138e3d4a1 496 | client_nonce: ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f 497 | 91fdaeeb1 498 | client_keyshare_seed: 633b875d74d1556d2a2789309972b06db21dfcc4f5ad51d 499 | 7e74d783b7cfab8dc 500 | server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a 501 | 1e85ff80da12f982f 502 | blind_registration: 411bf1a62d119afe30df682b91a0a33d777972d4f2daa4b34 503 | ca527d597078153 504 | blind_login: c497fddf6056d241e6cf9fb7ac37c384f49b357a221eb0a802c989b9 505 | 942256c1 506 | ~~~ 507 | 508 | #### Intermediate Values 509 | 510 | ~~~ 511 | client_public_key: 03b218507d978c3db570ca994aaf36695a731ddb2db272c817 512 | f79746fc37ae5214 513 | auth_key: 5bd4be1602516092dc5078f8d699f5721dc1720a49fb80d8e5c16377abd 514 | 0987b 515 | randomized_password: 06be0a1a51d56557a3adad57ba29c5510565dcd8b5078fa3 516 | 19151b9382258fb0 517 | envelope: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6567 518 | 0e51fad30bbcfc1f8eda0211553ab9aaf26345ad59a128e80188f035fe4924fad67b8 519 | handshake_secret: 83a932431a8f25bad042f008efa2b07c6cd0faa8285f335b636 520 | 3546a9f9b235f 521 | server_mac_key: 13e928581febfad28855e3e7f03306d61bd69489686f621535d44 522 | a1365b73b0d 523 | client_mac_key: afdc53910c25183b08b930e6953c35b3466276736d9de2e9c5efa 524 | f150f4082c5 525 | oprf_key: 2dfb5cb9aa1476093be74ca0d43e5b02862a05f5d6972614d7433acdc66 526 | f7f31 527 | ~~~ 528 | 529 | #### Output Values 530 | 531 | ~~~ 532 | registration_request: 029e949a29cfa0bf7c1287333d2fb3dc586c41aa652f507 533 | 0d26a5315a1b50229f8 534 | registration_response: 0350d3694c00978f00a5ce7cd08a00547e4ab5fb5fc2b2 535 | f6717cdaa6c89136efef035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd 536 | 9f2092d6067784874 537 | registration_upload: 03b218507d978c3db570ca994aaf36695a731ddb2db272c8 538 | 17f79746fc37ae52147f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea0 539 | 29148b2803aafa921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6 540 | 5670e51fad30bbcfc1f8eda0211553ab9aaf26345ad59a128e80188f035fe4924fad6 541 | 7b8 542 | KE1: 037342f0bcb3ecea754c1e67576c86aa90c1de3875f390ad599a26686cdfee6e 543 | 07ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1022 544 | ed3f32f318f81bab80da321fecab3cd9b6eea11a95666dfa6beeaab321280b6 545 | KE2: 0246da9fe4d41d5ba69faa6c509a1d5bafd49a48615a47a8dd4b0823cc147648 546 | 1138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d2f0 547 | c547f70deaeca54d878c14c1aa5e1ab405dec833777132eea905c2fbb12504a67dcbe 548 | 0e66740c76b62c13b04a38a77926e19072953319ec65e41f9bfd2ae26837b6ce688bf 549 | 9af2542f04eec9ab96a1b9328812dc2f5c89182ed47fead61f09f71cd9960ecef2fe0 550 | d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a103c1701353219b53acf33 551 | 7bf6456a83cefed8f563f1040b65afbf3b65d3bc9a19b50a73b145bc87a157e8c58c0 552 | 342e2047ee22ae37b63db17e0a82a30fcc4ecf7b 553 | KE3: e97cab4433aa39d598e76f13e768bba61c682947bdcf9936035e8a3a3ebfb66e 554 | export_key: c3c9a1b0e33ac84dd83d0b7e8af6794e17e7a3caadff289fbd9dc769a 555 | 853c64b 556 | session_key: 484ad345715ccce138ca49e4ea362c6183f0949aaaa1125dc3bc3f80 557 | 876e7cd1 558 | ~~~ 559 | 560 | ### OPAQUE-3DH Real Test Vector 6 561 | 562 | #### Configuration 563 | 564 | ~~~ 565 | OPRF: P256-SHA256 566 | Hash: SHA256 567 | KSF: Identity 568 | KDF: HKDF-SHA256 569 | MAC: HMAC-SHA256 570 | Group: P256_XMD:SHA-256_SSWU_RO_ 571 | Context: 4f50415155452d504f43 572 | Nh: 32 573 | Npk: 33 574 | Nsk: 32 575 | Nm: 32 576 | Nx: 32 577 | Nok: 32 578 | ~~~ 579 | 580 | #### Input Values 581 | 582 | ~~~ 583 | client_identity: 616c696365 584 | server_identity: 626f62 585 | oprf_seed: 62f60b286d20ce4fd1d64809b0021dad6ed5d52a2c8cf27ae6582543a0 586 | a8dce2 587 | credential_identifier: 31323334 588 | password: 436f7272656374486f72736542617474657279537461706c65 589 | envelope_nonce: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebd 590 | cf65670e51f 591 | masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80 592 | f612fdfc6d 593 | server_private_key: c36139381df63bfc91c850db0b9cfbec7a62e86d80040a41a 594 | a7725bf0e79d5e5 595 | server_public_key: 035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9 596 | f2092d6067784874 597 | server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e 598 | 138e3d4a1 599 | client_nonce: ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f 600 | 91fdaeeb1 601 | client_keyshare_seed: 633b875d74d1556d2a2789309972b06db21dfcc4f5ad51d 602 | 7e74d783b7cfab8dc 603 | server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a 604 | 1e85ff80da12f982f 605 | blind_registration: 411bf1a62d119afe30df682b91a0a33d777972d4f2daa4b34 606 | ca527d597078153 607 | blind_login: c497fddf6056d241e6cf9fb7ac37c384f49b357a221eb0a802c989b9 608 | 942256c1 609 | ~~~ 610 | 611 | #### Intermediate Values 612 | 613 | ~~~ 614 | client_public_key: 03b218507d978c3db570ca994aaf36695a731ddb2db272c817 615 | f79746fc37ae5214 616 | auth_key: 5bd4be1602516092dc5078f8d699f5721dc1720a49fb80d8e5c16377abd 617 | 0987b 618 | randomized_password: 06be0a1a51d56557a3adad57ba29c5510565dcd8b5078fa3 619 | 19151b9382258fb0 620 | envelope: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6567 621 | 0e51f4d7773a36a208a866301dbb2858e40dc5638017527cf91aef32d3848eebe0971 622 | handshake_secret: 80bdcc498f22de492e90ee8101fcc7c101e158dd49c77f7c283 623 | 816ae329ed62f 624 | server_mac_key: 0f82432fbdb5b90daf27a91a3acc42299a9590dba1b77932c2207 625 | b4cb3d4a157 626 | client_mac_key: 7f629eb0b1b69979b07ca1f564b3e92ed22f07569fd1d11725d93 627 | e46731fbe71 628 | oprf_key: 2dfb5cb9aa1476093be74ca0d43e5b02862a05f5d6972614d7433acdc66 629 | f7f31 630 | ~~~ 631 | 632 | #### Output Values 633 | 634 | ~~~ 635 | registration_request: 029e949a29cfa0bf7c1287333d2fb3dc586c41aa652f507 636 | 0d26a5315a1b50229f8 637 | registration_response: 0350d3694c00978f00a5ce7cd08a00547e4ab5fb5fc2b2 638 | f6717cdaa6c89136efef035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd 639 | 9f2092d6067784874 640 | registration_upload: 03b218507d978c3db570ca994aaf36695a731ddb2db272c8 641 | 17f79746fc37ae52147f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea0 642 | 29148b2803aafa921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6 643 | 5670e51f4d7773a36a208a866301dbb2858e40dc5638017527cf91aef32d3848eebe0 644 | 971 645 | KE1: 037342f0bcb3ecea754c1e67576c86aa90c1de3875f390ad599a26686cdfee6e 646 | 07ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1022 647 | ed3f32f318f81bab80da321fecab3cd9b6eea11a95666dfa6beeaab321280b6 648 | KE2: 0246da9fe4d41d5ba69faa6c509a1d5bafd49a48615a47a8dd4b0823cc147648 649 | 1138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d2f0 650 | c547f70deaeca54d878c14c1aa5e1ab405dec833777132eea905c2fbb12504a67dcbe 651 | 0e66740c76b62c13b04a38a77926e19072953319ec65e41f9bfd2ae268d7f10604202 652 | 1c80300e4c6f585980cf39fc51a4a6bba41b0729f9b240c729e5671cd9960ecef2fe0 653 | d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a103c1701353219b53acf33 654 | 7bf6456a83cefed8f563f1040b65afbf3b65d3bc9a19b84922c7e5d074838a8f27859 655 | 2c53f61fb59f031e85ad480c0c71086b871e1b24 656 | KE3: 46833578cee137775f6be3f01b80748daac5a694101ad0e9e7025480552da56a 657 | export_key: c3c9a1b0e33ac84dd83d0b7e8af6794e17e7a3caadff289fbd9dc769a 658 | 853c64b 659 | session_key: 27766fabd8dd88ff37fbd0ef1a491e601d10d9f016c2b28c4bd1b0fb 660 | 7511a3c3 661 | ~~~ 662 | 663 | ## Fake Test Vectors {#fake-vectors} 664 | 665 | ### OPAQUE-3DH Fake Test Vector 1 666 | 667 | #### Configuration 668 | 669 | ~~~ 670 | OPRF: ristretto255-SHA512 671 | Hash: SHA512 672 | KSF: Identity 673 | KDF: HKDF-SHA512 674 | MAC: HMAC-SHA512 675 | Group: ristretto255 676 | Context: 4f50415155452d504f43 677 | Nh: 64 678 | Npk: 32 679 | Nsk: 32 680 | Nm: 64 681 | Nx: 64 682 | Nok: 32 683 | ~~~ 684 | 685 | #### Input Values 686 | 687 | ~~~ 688 | client_identity: 616c696365 689 | server_identity: 626f62 690 | oprf_seed: 743fc168d1f826ad43738933e5adb23da6fb95f95a1b069f0daa0522d0 691 | a78b617f701fc6aa46d3e7981e70de7765dfcd6b1e13e3369a582eb8dc456b10aa53b 692 | 0 693 | credential_identifier: 31323334 694 | masking_nonce: 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c61 695 | 9e27b6e5a6 696 | client_private_key: 2b98980aa95ab53a0f39f0291903d2fdf04b00c167f081416 697 | 9922df873002409 698 | client_public_key: 84f43f9492e19c22d8bdaa4447cc3d4db1cdb5427a9f852c47 699 | 07921212c36251 700 | server_private_key: c788585ae8b5ba2942b693b849be0c0426384e41977c18d2e 701 | 81fbe30fd7c9f06 702 | server_public_key: 825f832667480f08b0c9069da5083ac4d0e9ee31b49c4e0310 703 | 031fea04d52966 704 | server_nonce: 1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813 705 | 055ae2d12 706 | client_keyshare_seed: a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276 707 | d1e15bdeb4c355e94 708 | server_keyshare_seed: 360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc 709 | 5b4f6b62df07f78c2 710 | masking_key: 39ebd51f0e39a07a1c2d2431995b0399bca9996c5d10014d6ebab445 711 | 3dc10ce5cef38ed3df6e56bfff40c2d8dd4671c2b4cf63c3d54860f31fe40220d690b 712 | b71 713 | KE1: b0a26dcaca2230b8f5e4b1bcab9c84b586140221bb8b2848486874b0be448905 714 | 42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0ab641d 715 | 7f20a245a09f1d4dbb6e301661af7f352beb0791d055e48d3645232f77f 716 | ~~~ 717 | 718 | #### Output Values 719 | 720 | ~~~ 721 | KE2: 928f79ad8df21963e91411b9f55165ba833dea918f441db967cdc09521d22925 722 | 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a632b5a 723 | b1bff96636144faa4f9f9afaac75dd88ea99cf5175902ae3f3b2195693f165f11929b 724 | a510a5978e64dcdabecbd7ee1e4380ce270e58fea58e6462d92964a1aaef72698bca1 725 | c673baeb04cc2bf7de5f3c2f5553464552d3a0f7698a9ca7f9c5e70c6cb1f706b2f17 726 | 5ab9d04bbd13926e816b6811a50b4aafa9799d5ed7971e10f6eeab2a7a420bf09da9b 727 | 27a4639645622c46358de9cf7ae813055ae2d1298251c5ba55f6b0b2d58d9ff0c88fe 728 | 4176484be62a96db6e2a8c4d431bd1bf27fe6c1d0537603835217d42ebf7b25819827 729 | 32e74892fd28211b31ed33863f0beaf75ba6f59474c0aaf9d78a60a9b2f4cd24d7ab5 730 | 4131b3c8efa192df6b72db4c 731 | ~~~ 732 | 733 | ### OPAQUE-3DH Fake Test Vector 2 734 | 735 | #### Configuration 736 | 737 | ~~~ 738 | OPRF: ristretto255-SHA512 739 | Hash: SHA512 740 | KSF: Identity 741 | KDF: HKDF-SHA512 742 | MAC: HMAC-SHA512 743 | Group: curve25519 744 | Context: 4f50415155452d504f43 745 | Nh: 64 746 | Npk: 32 747 | Nsk: 32 748 | Nm: 64 749 | Nx: 64 750 | Nok: 32 751 | ~~~ 752 | 753 | #### Input Values 754 | 755 | ~~~ 756 | client_identity: 616c696365 757 | server_identity: 626f62 758 | oprf_seed: 66e650652a8266b2205f31fdd68adeb739a05b5e650b19e7edc75e734a 759 | 1296d6088188ca46c31ae8ccbd42a52ed338c06e53645387a7efbc94b6a0449526155 760 | e 761 | credential_identifier: 31323334 762 | masking_nonce: 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c61 763 | 9e27b6e5a6 764 | client_private_key: 288bf63470199221847bb035d99f96531adf8badd14cb1571 765 | b48f7a506649660 766 | client_public_key: 3c64a3153854cc9f0c23aab3c1a19106ec8bab4730736d1d00 767 | 3880a1d5a59005 768 | server_private_key: 30fbe7e830be1fe8d2187c97414e3826040cbe49b893b6422 769 | 9bab5e85a588846 770 | server_public_key: 78b3040047ff26572a7619617601a61b9c81899bee92f00cfc 771 | aa5eed96863555 772 | server_nonce: 1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813 773 | 055ae2d12 774 | client_keyshare_seed: a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276 775 | d1e15bdeb4c355e94 776 | server_keyshare_seed: 360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc 777 | 5b4f6b62df07f78c2 778 | masking_key: 79ad2621b0757a447dff7108a8ae20a068ce67872095620f415ea611 779 | c9dcc04972fa359538cd2fd6528775ca775487b2b56db642049b8a90526b975a38484 780 | c6a 781 | KE1: b0a26dcaca2230b8f5e4b1bcab9c84b586140221bb8b2848486874b0be448905 782 | 42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0ac059b 783 | 7ba2aec863933ae48816360c7a9022e83d822704f3b0b86c0502a66e574 784 | ~~~ 785 | 786 | #### Output Values 787 | 788 | ~~~ 789 | KE2: 6606b6fedbb33f19a81a1feb5149c600fe77252f58acd3080d7504d3dad4922f 790 | 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a67db39 791 | 8c0f65d8c298eac430abdae4c80e82b552fb940c00f0cbcea853c0f96c1c15099f3d4 792 | b0e83ecc249613116d605b8d77bb68bdf76994c2bc507e2dcae4176f00afed68ad25c 793 | f3040a0e991acece31ca532117f5c12816997372ff031ad04ebcdce06c501da24e7b4 794 | db95343456e2ed260895ec362694230a1fa20e24a9c71e10f6eeab2a7a420bf09da9b 795 | 27a4639645622c46358de9cf7ae813055ae2d122d9055eb8f83e1b497370adad5cc2a 796 | 417bf9be436a792def0c7b7ccb92b9e275d7c663104ea4655bd70570d975c05351655 797 | d55fbfb392286edb55600a23b55ce18f8c60e0d1960c960412dd08eabc81ba7ca8ae2 798 | b04aad65462321f51c298010 799 | ~~~ 800 | 801 | ### OPAQUE-3DH Fake Test Vector 3 802 | 803 | #### Configuration 804 | 805 | ~~~ 806 | OPRF: P256-SHA256 807 | Hash: SHA256 808 | KSF: Identity 809 | KDF: HKDF-SHA256 810 | MAC: HMAC-SHA256 811 | Group: P256_XMD:SHA-256_SSWU_RO_ 812 | Context: 4f50415155452d504f43 813 | Nh: 32 814 | Npk: 33 815 | Nsk: 32 816 | Nm: 32 817 | Nx: 32 818 | Nok: 32 819 | ~~~ 820 | 821 | #### Input Values 822 | 823 | ~~~ 824 | client_identity: 616c696365 825 | server_identity: 626f62 826 | oprf_seed: bb1cd59e16ac09bc0cb6d528541695d7eba2239b1613a3db3ade77b362 827 | 80f725 828 | credential_identifier: 31323334 829 | masking_nonce: 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c61 830 | 9e27b6e5a6 831 | client_private_key: d423b87899fc61d014fc8330a4e26190fcfa470a3afe59243 832 | 24294af7dbbc1dd 833 | client_public_key: 03b81708eae026a9370616c22e1e8542fe9dbebd36ce8a2661 834 | b708e9628f4a57fc 835 | server_private_key: 34fbe7e830be1fe8d2187c97414e3826040cbe49b893b6422 836 | 9bab5e85a5888c7 837 | server_public_key: 0221e034c0e202fe883dcfc96802a7624166fed4cfcab4ae30 838 | cf5f3290d01c88bf 839 | server_nonce: 1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813 840 | 055ae2d12 841 | client_keyshare_seed: a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276 842 | d1e15bdeb4c355e94 843 | server_keyshare_seed: 360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc 844 | 5b4f6b62df07f78c2 845 | masking_key: caecc6ccb4cae27cb54d8f3a1af1bac52a3d53107ce08497cdd362b1 846 | 992e4e5e 847 | KE1: 0396875da2b4f7749bba411513aea02dc514a48d169d8a9531bd61d3af3fa9ba 848 | ae42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0a021 849 | 47a6583983cc9973b5082db5f5070890cb373d70f7ac1b41ed2305361009784 850 | ~~~ 851 | 852 | #### Output Values 853 | 854 | ~~~ 855 | KE2: 0201198dcd13f9792eb75dcfa815f61b049abfe2e3e9456d4bbbceec5f442efd 856 | 049c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a6fac 857 | da65ce0a97b9085e7af07f61fd3fdd046d257cbf2183ce8766090b8041a8bf28d79dd 858 | 4c9031ddc75bb6ddb4c291e639937840e3d39fc0d5a3d6e7723c09f7945df485bcf9a 859 | efe3fe82d149e84049e259bb5b33d6a2ff3b25e4bfb7eff0962821e10f6eeab2a7a42 860 | 0bf09da9b27a4639645622c46358de9cf7ae813055ae2d12023f82bbb24e75b8683fd 861 | 13b843cd566efae996cd0016cffdcc24ee2bc937d026f80144878749a69565b433c10 862 | 40aff67e94f79345de888a877422b9bbe21ec329 863 | ~~~ 864 | 865 | -------------------------------------------------------------------------------- /poc/vectors/vectors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "config": { 4 | "Context": "4f50415155452d504f43", 5 | "Fake": "False", 6 | "Group": "ristretto255", 7 | "Hash": "SHA512", 8 | "KDF": "HKDF-SHA512", 9 | "KSF": "Identity", 10 | "MAC": "HMAC-SHA512", 11 | "Name": "3DH", 12 | "Nh": "64", 13 | "Nm": "64", 14 | "Nok": "32", 15 | "Npk": "32", 16 | "Nsk": "32", 17 | "Nx": "64", 18 | "OPRF": "ristretto255-SHA512" 19 | }, 20 | "inputs": { 21 | "blind_login": "6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4b0790308", 22 | "blind_registration": "76cfbfe758db884bebb33582331ba9f159720ca8784a2a070a265d9c2d6abe01", 23 | "client_keyshare_seed": "82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf1091933541936304b", 24 | "client_nonce": "da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc", 25 | "credential_identifier": "31323334", 26 | "envelope_nonce": "ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfec", 27 | "masking_nonce": "38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d", 28 | "oprf_seed": "f433d0227b0b9dd54f7c4422b600e764e47fb503f1f9a0f0a47c6606b054a7fdc65347f1a08f277e22358bbabe26f823fca82c7848e9a75661f4ec5d5c1989ef", 29 | "password": "436f7272656374486f72736542617474657279537461706c65", 30 | "server_keyshare_seed": "05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a1e85ff80da12f982f", 31 | "server_nonce": "71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1", 32 | "server_private_key": "47451a85372f8b3537e249d7b54188091fb18edde78094b43e2ba42b5eb89f0d", 33 | "server_public_key": "b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a382c9b79df1a78" 34 | }, 35 | "intermediates": { 36 | "auth_key": "6cd32316f18d72a9a927a83199fa030663a38ce0c11fbaef82aa90037730494fc555c4d49506284516edd1628c27965b7555a4ebfed2223199f6c67966dde822", 37 | "client_mac_key": "91750adbac54a5e8e53b4c233cc8d369fe83b0de1b6a3cd85575eeb0bb01a6a90a086a2cf5fe75fff2a9379c30ba9049510a33b5b0b1444a88800fc3eee2260d", 38 | "client_public_key": "76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbaccafb57ac5c3675", 39 | "envelope": "ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfec634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b8d9e9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5", 40 | "handshake_secret": "81263cb85a0cfa12450f0f388de4e92291ec4c7c7a0878b624550ff528726332f1298fc6cc822a432c89504347c7a2ccd70316ae3da6a15e0399e6db3f7c1b12", 41 | "masking_key": "1ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa82785c5", 42 | "oprf_key": "5d4c6a8b7c7138182afb4345d1fae6a9f18a1744afbcc3854f8f5a2b4b4c6d05", 43 | "randomized_password": "aac48c25ab036e30750839d31d6e73007344cb1155289fb7d329beb932e9adeea73d5d5c22a0ce1952f8aba6d66007615cd1698d4ac85ef1fcf150031d1435d9", 44 | "server_mac_key": "0d36b26cfe38f51f804f0a9361818f32ee1ce2a4e5578653b527184af058d3b2d8075c296fd84d24677913d1baa109290cd81a13ed383f9091a3804e65298dfc" 45 | }, 46 | "outputs": { 47 | "KE1": "c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44dda7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc6e29bee50701498605b2c085d7b241ca15ba5c32027dd21ba420b94ce60da326", 48 | "KE2": "7e308140890bcde30cbcea28b01ea1ecfbd077cff62c4def8efa075aabcbb47138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6dd6ec60bcdb26dc455ddf3e718f1020490c192d70dfc7e403981179d8073d1146a4f9aa1ced4e4cd984c657eb3b54ced3848326f70331953d91b02535af44d9fedc80188ca46743c52786e0382f95ad85c08f6afcd1ccfbff95e2bdeb015b166c6b20b92f832cc6df01e0b86a7efd92c1c804ff865781fa93f2f20b446c8371b671cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1c4f62198a9d6fa9170c42c3c71f1971b29eb1d5d0bd733e40816c91f7912cc4a660c48dae03e57aaa38f3d0cffcfc21852ebc8b405d15bd6744945ba1a93438a162b6111699d98a16bb55b7bdddfe0fc5608b23da246e7bd73b47369169c5c90", 49 | "KE3": "4455df4f810ac31a6748835888564b536e6da5d9944dfea9e34defb9575fe5e2661ef61d2ae3929bcf57e53d464113d364365eb7d1a57b629707ca48da18e442", 50 | "export_key": "1ef15b4fa99e8a852412450ab78713aad30d21fa6966c9b8c9fb3262a970dc62950d4dd4ed62598229b1b72794fc0335199d9f7fcc6eaedde92cc04870e63f16", 51 | "registration_request": "5059ff249eb1551b7ce4991f3336205bde44a105a032e747d21bf382e75f7a71", 52 | "registration_response": "7408a268083e03abc7097fc05b587834539065e86fb0c7b6342fcf5e01e5b019b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a382c9b79df1a78", 53 | "registration_upload": "76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbaccafb57ac5c36751ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa82785c5ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfec634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b8d9e9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5", 54 | "session_key": "42afde6f5aca0cfa5c163763fbad55e73a41db6b41bc87b8e7b62214a8eedc6731fa3cb857d657ab9b3764b89a84e91ebcb4785166fbb02cedfcbdfda215b96f" 55 | } 56 | }, 57 | { 58 | "config": { 59 | "Context": "4f50415155452d504f43", 60 | "Fake": "False", 61 | "Group": "ristretto255", 62 | "Hash": "SHA512", 63 | "KDF": "HKDF-SHA512", 64 | "KSF": "Identity", 65 | "MAC": "HMAC-SHA512", 66 | "Name": "3DH", 67 | "Nh": "64", 68 | "Nm": "64", 69 | "Nok": "32", 70 | "Npk": "32", 71 | "Nsk": "32", 72 | "Nx": "64", 73 | "OPRF": "ristretto255-SHA512" 74 | }, 75 | "inputs": { 76 | "blind_login": "6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4b0790308", 77 | "blind_registration": "76cfbfe758db884bebb33582331ba9f159720ca8784a2a070a265d9c2d6abe01", 78 | "client_identity": "616c696365", 79 | "client_keyshare_seed": "82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf1091933541936304b", 80 | "client_nonce": "da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc", 81 | "credential_identifier": "31323334", 82 | "envelope_nonce": "ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfec", 83 | "masking_nonce": "38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d", 84 | "oprf_seed": "f433d0227b0b9dd54f7c4422b600e764e47fb503f1f9a0f0a47c6606b054a7fdc65347f1a08f277e22358bbabe26f823fca82c7848e9a75661f4ec5d5c1989ef", 85 | "password": "436f7272656374486f72736542617474657279537461706c65", 86 | "server_identity": "626f62", 87 | "server_keyshare_seed": "05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a1e85ff80da12f982f", 88 | "server_nonce": "71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1", 89 | "server_private_key": "47451a85372f8b3537e249d7b54188091fb18edde78094b43e2ba42b5eb89f0d", 90 | "server_public_key": "b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a382c9b79df1a78" 91 | }, 92 | "intermediates": { 93 | "auth_key": "6cd32316f18d72a9a927a83199fa030663a38ce0c11fbaef82aa90037730494fc555c4d49506284516edd1628c27965b7555a4ebfed2223199f6c67966dde822", 94 | "client_mac_key": "f816fe2914f7c5b29852385615d7c7f31ac122adf202d7ccd497606d7aabd48930323d1d02b1cc9ecd456c4de6f46c7950becb18bffd921dd5876381b5486ffe", 95 | "client_public_key": "76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbaccafb57ac5c3675", 96 | "envelope": "ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfec1ac902dc5589e9a5f0de56ad685ea8486210ef41449cd4d8712828913c5d2b680b2b3af4a26c765cff329bfb66d38ecf1d6cfa9e7a73c222c6efe0d9520f7d7c", 97 | "handshake_secret": "5e723bed1e5276de2503419eba9da61ead573109c401226832398c7e08155b885bfe7bc93451f9d887a0c1d0c19233e40a8e47b347a9ac3907f94032a4cff64f", 98 | "masking_key": "1ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa82785c5", 99 | "oprf_key": "5d4c6a8b7c7138182afb4345d1fae6a9f18a1744afbcc3854f8f5a2b4b4c6d05", 100 | "randomized_password": "aac48c25ab036e30750839d31d6e73007344cb1155289fb7d329beb932e9adeea73d5d5c22a0ce1952f8aba6d66007615cd1698d4ac85ef1fcf150031d1435d9", 101 | "server_mac_key": "dad66bb9251073d17a13f8e5500f36e5998e3cde520ca0738e7085af62fd97812eb79a745c94d0bf8a6ac17f980cf435504cf64041eeb6bb237796d2c7f81e9a" 102 | }, 103 | "outputs": { 104 | "KE1": "c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44dda7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc6e29bee50701498605b2c085d7b241ca15ba5c32027dd21ba420b94ce60da326", 105 | "KE2": "7e308140890bcde30cbcea28b01ea1ecfbd077cff62c4def8efa075aabcbb47138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6dd6ec60bcdb26dc455ddf3e718f1020490c192d70dfc7e403981179d8073d1146a4f9aa1ced4e4cd984c657eb3b54ced3848326f70331953d91b02535af44d9fea502150b67fe36795dd8914f164e49f81c7688a38928372134b7dccd50e09f8fed9518b7b2f94835b3c4fe4c8475e7513f20eb97ff0568a39caee3fd6251876f71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1c4f62198a9d6fa9170c42c3c71f1971b29eb1d5d0bd733e40816c91f7912cc4a292371e7809a9031743e943fb3b56f51de903552fc91fba4e7419029951c3970b2e2f0a9dea218d22e9e4e0000855bb6421aa3610d6fc0f4033a6517030d4341", 106 | "KE3": "7a026de1d6126905736c3f6d92463a08d209833eb793e46d0f7f15b3e0f62c7643763c02bbc6b8d3d15b63250cae98171e9260f1ffa789750f534ac11a0176d5", 107 | "export_key": "1ef15b4fa99e8a852412450ab78713aad30d21fa6966c9b8c9fb3262a970dc62950d4dd4ed62598229b1b72794fc0335199d9f7fcc6eaedde92cc04870e63f16", 108 | "registration_request": "5059ff249eb1551b7ce4991f3336205bde44a105a032e747d21bf382e75f7a71", 109 | "registration_response": "7408a268083e03abc7097fc05b587834539065e86fb0c7b6342fcf5e01e5b019b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a382c9b79df1a78", 110 | "registration_upload": "76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbaccafb57ac5c36751ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa82785c5ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfec1ac902dc5589e9a5f0de56ad685ea8486210ef41449cd4d8712828913c5d2b680b2b3af4a26c765cff329bfb66d38ecf1d6cfa9e7a73c222c6efe0d9520f7d7c", 111 | "session_key": "ae7951123ab5befc27e62e63f52cf472d6236cb386c968cc47b7e34f866aa4bc7638356a73cfce92becf39d6a7d32a1861f12130e824241fe6cab34fbd471a57" 112 | } 113 | }, 114 | { 115 | "config": { 116 | "Context": "4f50415155452d504f43", 117 | "Fake": "False", 118 | "Group": "curve25519", 119 | "Hash": "SHA512", 120 | "KDF": "HKDF-SHA512", 121 | "KSF": "Identity", 122 | "MAC": "HMAC-SHA512", 123 | "Name": "3DH", 124 | "Nh": "64", 125 | "Nm": "64", 126 | "Nok": "32", 127 | "Npk": "32", 128 | "Nsk": "32", 129 | "Nx": "64", 130 | "OPRF": "ristretto255-SHA512" 131 | }, 132 | "inputs": { 133 | "blind_login": "6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4b0790308", 134 | "blind_registration": "c575731ffe1cb0ca5ba63b42c4699767b8b9ab78ba39316ee04baddb2034a70a", 135 | "client_keyshare_seed": "82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf1091933541936304b", 136 | "client_nonce": "da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc", 137 | "credential_identifier": "31323334", 138 | "envelope_nonce": "40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0b", 139 | "masking_nonce": "38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d", 140 | "oprf_seed": "a78342ab84d3d30f08d5a9630c79bf311c31ed7f85d9d4959bf492ec67a0eec8a67dfbf4497248eebd49e878aab173e5e4ff76354288fdd53e949a5f7c9f7f1b", 141 | "password": "436f7272656374486f72736542617474657279537461706c65", 142 | "server_keyshare_seed": "05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a1e85ff80da12f982f", 143 | "server_nonce": "71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1", 144 | "server_private_key": "c06139381df63bfc91c850db0b9cfbec7a62e86d80040a41aa7725bf0e79d564", 145 | "server_public_key": "a41e28269b4e97a66468cc00c5a57753e192e152766989770688aa90486ef031" 146 | }, 147 | "intermediates": { 148 | "auth_key": "7e880ab484f750e80e6f839d975aff476070ce65066d85ea62523d1d5764739d91307fac47186a4ab935e6a5c7f70cb47faa9473311947502c022cc67ae9440c", 149 | "client_mac_key": "b1ee7ce52dbd0ab72872924ff11596cb196bbabfc319e74aca78ade54a0f74dd15dcf5621f6d2e79161b0c9b701381d494836dedbb86e584a65b34267a370e01", 150 | "client_public_key": "0936ea94ab030ec332e29050d266c520e916731a052d05ced7e0cfe751142b48", 151 | "envelope": "40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0b20c1e81fef28e92e897ca8287d49a55075b47c3988ff0fff367d79a3e350ccac150b4a3ff48b4770c8e84e437b3d4e68d2b95833f7788f7eb93fa6a8afb85ecb", 152 | "handshake_secret": "178c8c15e025252380c3edb1c6ad8ac52573b38d536099e2f865786f5e31c642608550c0c6f281c37ce259667dd72768af31630e0eb36f1096a2e6421c2aa163", 153 | "masking_key": "6d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9ef53c35", 154 | "oprf_key": "62ef7f7d9506a14600c34f642aaf6ef8019cc82a6755db4fded5248ea146030a", 155 | "randomized_password": "3a602c295a9c323d9362fe286f104567ed6862b25dbe30fada844f19e41cf40047424b7118e15dc2c1a815a70fea5c8de6c30aa61440cd4b4b5e8f3963fbb2e1", 156 | "server_mac_key": "f3c6a8e069c54bb0d8905139f723c9e22f5c662dc08848243a6654c8223800019b9823523d84da2ef67ca1c14277630aace464c113be8a0a658c39e181a8bb71" 157 | }, 158 | "outputs": { 159 | "KE1": "c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44dda7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc10a83b9117d3798cb2957fbdb0268a0d63dbf9d66bde5c00c78affd80026c911", 160 | "KE2": "9a0e5a1514f62e005ea098b0d8cf6750e358c4389e6add1c52aed9500fa19d0038fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d22cc31127d6f0096755be3c3d2dd6287795c317aeea10c9485bf4f419a786642c19a8f151ceb5e8767d175248c62c017de94057398d28bf0ed00d1b50ee4f812fd9afddf98af8cd58067ca43b0633b6cadd0e9d987f89623fed4d3583bdf6910c425600e90dab3c6b3513188a465461a67f6bbc47aeba808f7f7e2c6d66f5c3271cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a141f55f0bef355cfb34ccd468fdacad75865ee7efef95f4cb6c25d477f720502676f06a3b806da262139bf3fa76a1090b94dac78bc3bc6f8747d5b35acf94eff3ec2ebe7d49b8cf16be64120b279fe92664e47be5da7e60f08f12e91192652f79", 161 | "KE3": "550e923829a544496d8316c490da2b979b78c730dd75be3a17f237a26432c19fbba54b6a0467b1c22ecbd6794bc5fa5b04215ba1ef974c6b090baa42c5bb984f", 162 | "export_key": "9dec51d6d0f6ce7e4345f10961053713b07310cc2e45872f57bbd2fe5070fdf0fb5b77c7ddaa2f3dc5c35132df7417ad7fefe0f690ad266e5a54a21d045c9c38", 163 | "registration_request": "26f3dbfd76b8e5f85b4da604f42889a7d4b1bc919f655381a67de02c59fd5436", 164 | "registration_response": "506e8f1b89c098fb89b5b6210a05f7898cafdaea221761e8d5272fc39e0f9f08a41e28269b4e97a66468cc00c5a57753e192e152766989770688aa90486ef031", 165 | "registration_upload": "0936ea94ab030ec332e29050d266c520e916731a052d05ced7e0cfe751142b486d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9ef53c3540d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0b20c1e81fef28e92e897ca8287d49a55075b47c3988ff0fff367d79a3e350ccac150b4a3ff48b4770c8e84e437b3d4e68d2b95833f7788f7eb93fa6a8afb85ecb", 166 | "session_key": "fd2fdd07c1bcc88e81c1b1d1de5ad62dfdef1c0b8209ff9d671e1fac55ce9c34d381c1fb2703ff53a797f77daccbe33047ccc167b8105171e10ec962eea203aa" 167 | } 168 | }, 169 | { 170 | "config": { 171 | "Context": "4f50415155452d504f43", 172 | "Fake": "False", 173 | "Group": "curve25519", 174 | "Hash": "SHA512", 175 | "KDF": "HKDF-SHA512", 176 | "KSF": "Identity", 177 | "MAC": "HMAC-SHA512", 178 | "Name": "3DH", 179 | "Nh": "64", 180 | "Nm": "64", 181 | "Nok": "32", 182 | "Npk": "32", 183 | "Nsk": "32", 184 | "Nx": "64", 185 | "OPRF": "ristretto255-SHA512" 186 | }, 187 | "inputs": { 188 | "blind_login": "6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4b0790308", 189 | "blind_registration": "c575731ffe1cb0ca5ba63b42c4699767b8b9ab78ba39316ee04baddb2034a70a", 190 | "client_identity": "616c696365", 191 | "client_keyshare_seed": "82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf1091933541936304b", 192 | "client_nonce": "da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc", 193 | "credential_identifier": "31323334", 194 | "envelope_nonce": "40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0b", 195 | "masking_nonce": "38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d", 196 | "oprf_seed": "a78342ab84d3d30f08d5a9630c79bf311c31ed7f85d9d4959bf492ec67a0eec8a67dfbf4497248eebd49e878aab173e5e4ff76354288fdd53e949a5f7c9f7f1b", 197 | "password": "436f7272656374486f72736542617474657279537461706c65", 198 | "server_identity": "626f62", 199 | "server_keyshare_seed": "05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a1e85ff80da12f982f", 200 | "server_nonce": "71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1", 201 | "server_private_key": "c06139381df63bfc91c850db0b9cfbec7a62e86d80040a41aa7725bf0e79d564", 202 | "server_public_key": "a41e28269b4e97a66468cc00c5a57753e192e152766989770688aa90486ef031" 203 | }, 204 | "intermediates": { 205 | "auth_key": "7e880ab484f750e80e6f839d975aff476070ce65066d85ea62523d1d5764739d91307fac47186a4ab935e6a5c7f70cb47faa9473311947502c022cc67ae9440c", 206 | "client_mac_key": "1e1a8ba156aadc4a302f707d2193c9dab477b355f430d450dd407ce40dc75613f76ec33dec494f8a6bfdcf951eb060dac33e6572c693954fe92e33730c9ab0a2", 207 | "client_public_key": "0936ea94ab030ec332e29050d266c520e916731a052d05ced7e0cfe751142b48", 208 | "envelope": "40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0bb4c0eab6143959a650c5f6b32acf162b1fbe95bb36c5c4f99df53865c4d3537d69061d80522d772cd0efdbe91f817f6bf7259a56e20b4eb9cbe9443702f4b759", 209 | "handshake_secret": "13e7dc6afa5334b9dfffe26bee3caf744ef4add176caee464cdeb3d37303b90de35a8bf095df84471ac77d705f12fe232f1571de1d6a001d3e80899873a142dc", 210 | "masking_key": "6d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9ef53c35", 211 | "oprf_key": "62ef7f7d9506a14600c34f642aaf6ef8019cc82a6755db4fded5248ea146030a", 212 | "randomized_password": "3a602c295a9c323d9362fe286f104567ed6862b25dbe30fada844f19e41cf40047424b7118e15dc2c1a815a70fea5c8de6c30aa61440cd4b4b5e8f3963fbb2e1", 213 | "server_mac_key": "a58135acfb2bde92d506cf59119729a6404ad94eba294e4b52a63baf58cfe03f21bcf735222c7f2c27a60bd958be7f6aed50dc03a78f64e7ae4ac1ff071b95aa" 214 | }, 215 | "outputs": { 216 | "KE1": "c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44dda7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc10a83b9117d3798cb2957fbdb0268a0d63dbf9d66bde5c00c78affd80026c911", 217 | "KE2": "9a0e5a1514f62e005ea098b0d8cf6750e358c4389e6add1c52aed9500fa19d0038fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d22cc31127d6f0096755be3c3d2dd6287795c317aeea10c9485bf4f419a786642c19a8f151ceb5e8767d175248c62c017de94057398d28bf0ed00d1b50ee4f812699bff7663be3c5d59de94d8e7e58817c7da005b39c25d25555c929e1c5cf6c1b82837b1367c839aab56a422c0d97719426a79a16f9869cf852100597b23b5a071cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a141f55f0bef355cfb34ccd468fdacad75865ee7efef95f4cb6c25d477f72050267cc22c87edbf3ecaca64cb33bc60dc3bfc551e365f0d46a7fed0e09d96f9afbb48868f5bb3c3e05a86ed8c9476fc22c58306c5a291be34388e09548ba9d70f39", 218 | "KE3": "d16344e791c3f18594d22ba068984fa18ec1e9bead662b75f66826ffd627932fcd1ec40cd01dcf5f63f4055ebe45c7717a57a833aad360256cf1e1c20c0eae1c", 219 | "export_key": "9dec51d6d0f6ce7e4345f10961053713b07310cc2e45872f57bbd2fe5070fdf0fb5b77c7ddaa2f3dc5c35132df7417ad7fefe0f690ad266e5a54a21d045c9c38", 220 | "registration_request": "26f3dbfd76b8e5f85b4da604f42889a7d4b1bc919f655381a67de02c59fd5436", 221 | "registration_response": "506e8f1b89c098fb89b5b6210a05f7898cafdaea221761e8d5272fc39e0f9f08a41e28269b4e97a66468cc00c5a57753e192e152766989770688aa90486ef031", 222 | "registration_upload": "0936ea94ab030ec332e29050d266c520e916731a052d05ced7e0cfe751142b486d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9ef53c3540d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0bb4c0eab6143959a650c5f6b32acf162b1fbe95bb36c5c4f99df53865c4d3537d69061d80522d772cd0efdbe91f817f6bf7259a56e20b4eb9cbe9443702f4b759", 223 | "session_key": "f6116d3aa0e4089a179713bad4d98ed5cb57e5443cae8d36ef78996fa60f3dc6e9fcdd63c001596b06dbc1285d80211035cc0e485506b3f7a650cbf78c5bffc9" 224 | } 225 | }, 226 | { 227 | "config": { 228 | "Context": "4f50415155452d504f43", 229 | "Fake": "False", 230 | "Group": "P256_XMD:SHA-256_SSWU_RO_", 231 | "Hash": "SHA256", 232 | "KDF": "HKDF-SHA256", 233 | "KSF": "Identity", 234 | "MAC": "HMAC-SHA256", 235 | "Name": "3DH", 236 | "Nh": "32", 237 | "Nm": "32", 238 | "Nok": "32", 239 | "Npk": "33", 240 | "Nsk": "32", 241 | "Nx": "32", 242 | "OPRF": "P256-SHA256" 243 | }, 244 | "inputs": { 245 | "blind_login": "c497fddf6056d241e6cf9fb7ac37c384f49b357a221eb0a802c989b9942256c1", 246 | "blind_registration": "411bf1a62d119afe30df682b91a0a33d777972d4f2daa4b34ca527d597078153", 247 | "client_keyshare_seed": "633b875d74d1556d2a2789309972b06db21dfcc4f5ad51d7e74d783b7cfab8dc", 248 | "client_nonce": "ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1", 249 | "credential_identifier": "31323334", 250 | "envelope_nonce": "a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf65670e51f", 251 | "masking_nonce": "38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d", 252 | "oprf_seed": "62f60b286d20ce4fd1d64809b0021dad6ed5d52a2c8cf27ae6582543a0a8dce2", 253 | "password": "436f7272656374486f72736542617474657279537461706c65", 254 | "server_keyshare_seed": "05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a1e85ff80da12f982f", 255 | "server_nonce": "71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1", 256 | "server_private_key": "c36139381df63bfc91c850db0b9cfbec7a62e86d80040a41aa7725bf0e79d5e5", 257 | "server_public_key": "035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9f2092d6067784874" 258 | }, 259 | "intermediates": { 260 | "auth_key": "5bd4be1602516092dc5078f8d699f5721dc1720a49fb80d8e5c16377abd0987b", 261 | "client_mac_key": "afdc53910c25183b08b930e6953c35b3466276736d9de2e9c5efaf150f4082c5", 262 | "client_public_key": "03b218507d978c3db570ca994aaf36695a731ddb2db272c817f79746fc37ae5214", 263 | "envelope": "a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf65670e51fad30bbcfc1f8eda0211553ab9aaf26345ad59a128e80188f035fe4924fad67b8", 264 | "handshake_secret": "83a932431a8f25bad042f008efa2b07c6cd0faa8285f335b6363546a9f9b235f", 265 | "masking_key": "7f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea029148b2803aaf", 266 | "oprf_key": "2dfb5cb9aa1476093be74ca0d43e5b02862a05f5d6972614d7433acdc66f7f31", 267 | "randomized_password": "06be0a1a51d56557a3adad57ba29c5510565dcd8b5078fa319151b9382258fb0", 268 | "server_mac_key": "13e928581febfad28855e3e7f03306d61bd69489686f621535d44a1365b73b0d" 269 | }, 270 | "outputs": { 271 | "KE1": "037342f0bcb3ecea754c1e67576c86aa90c1de3875f390ad599a26686cdfee6e07ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1022ed3f32f318f81bab80da321fecab3cd9b6eea11a95666dfa6beeaab321280b6", 272 | "KE2": "0246da9fe4d41d5ba69faa6c509a1d5bafd49a48615a47a8dd4b0823cc1476481138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d2f0c547f70deaeca54d878c14c1aa5e1ab405dec833777132eea905c2fbb12504a67dcbe0e66740c76b62c13b04a38a77926e19072953319ec65e41f9bfd2ae26837b6ce688bf9af2542f04eec9ab96a1b9328812dc2f5c89182ed47fead61f09f71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a103c1701353219b53acf337bf6456a83cefed8f563f1040b65afbf3b65d3bc9a19b50a73b145bc87a157e8c58c0342e2047ee22ae37b63db17e0a82a30fcc4ecf7b", 273 | "KE3": "e97cab4433aa39d598e76f13e768bba61c682947bdcf9936035e8a3a3ebfb66e", 274 | "export_key": "c3c9a1b0e33ac84dd83d0b7e8af6794e17e7a3caadff289fbd9dc769a853c64b", 275 | "registration_request": "029e949a29cfa0bf7c1287333d2fb3dc586c41aa652f5070d26a5315a1b50229f8", 276 | "registration_response": "0350d3694c00978f00a5ce7cd08a00547e4ab5fb5fc2b2f6717cdaa6c89136efef035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9f2092d6067784874", 277 | "registration_upload": "03b218507d978c3db570ca994aaf36695a731ddb2db272c817f79746fc37ae52147f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea029148b2803aafa921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf65670e51fad30bbcfc1f8eda0211553ab9aaf26345ad59a128e80188f035fe4924fad67b8", 278 | "session_key": "484ad345715ccce138ca49e4ea362c6183f0949aaaa1125dc3bc3f80876e7cd1" 279 | } 280 | }, 281 | { 282 | "config": { 283 | "Context": "4f50415155452d504f43", 284 | "Fake": "False", 285 | "Group": "P256_XMD:SHA-256_SSWU_RO_", 286 | "Hash": "SHA256", 287 | "KDF": "HKDF-SHA256", 288 | "KSF": "Identity", 289 | "MAC": "HMAC-SHA256", 290 | "Name": "3DH", 291 | "Nh": "32", 292 | "Nm": "32", 293 | "Nok": "32", 294 | "Npk": "33", 295 | "Nsk": "32", 296 | "Nx": "32", 297 | "OPRF": "P256-SHA256" 298 | }, 299 | "inputs": { 300 | "blind_login": "c497fddf6056d241e6cf9fb7ac37c384f49b357a221eb0a802c989b9942256c1", 301 | "blind_registration": "411bf1a62d119afe30df682b91a0a33d777972d4f2daa4b34ca527d597078153", 302 | "client_identity": "616c696365", 303 | "client_keyshare_seed": "633b875d74d1556d2a2789309972b06db21dfcc4f5ad51d7e74d783b7cfab8dc", 304 | "client_nonce": "ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1", 305 | "credential_identifier": "31323334", 306 | "envelope_nonce": "a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf65670e51f", 307 | "masking_nonce": "38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d", 308 | "oprf_seed": "62f60b286d20ce4fd1d64809b0021dad6ed5d52a2c8cf27ae6582543a0a8dce2", 309 | "password": "436f7272656374486f72736542617474657279537461706c65", 310 | "server_identity": "626f62", 311 | "server_keyshare_seed": "05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a1e85ff80da12f982f", 312 | "server_nonce": "71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a1", 313 | "server_private_key": "c36139381df63bfc91c850db0b9cfbec7a62e86d80040a41aa7725bf0e79d5e5", 314 | "server_public_key": "035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9f2092d6067784874" 315 | }, 316 | "intermediates": { 317 | "auth_key": "5bd4be1602516092dc5078f8d699f5721dc1720a49fb80d8e5c16377abd0987b", 318 | "client_mac_key": "7f629eb0b1b69979b07ca1f564b3e92ed22f07569fd1d11725d93e46731fbe71", 319 | "client_public_key": "03b218507d978c3db570ca994aaf36695a731ddb2db272c817f79746fc37ae5214", 320 | "envelope": "a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf65670e51f4d7773a36a208a866301dbb2858e40dc5638017527cf91aef32d3848eebe0971", 321 | "handshake_secret": "80bdcc498f22de492e90ee8101fcc7c101e158dd49c77f7c283816ae329ed62f", 322 | "masking_key": "7f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea029148b2803aaf", 323 | "oprf_key": "2dfb5cb9aa1476093be74ca0d43e5b02862a05f5d6972614d7433acdc66f7f31", 324 | "randomized_password": "06be0a1a51d56557a3adad57ba29c5510565dcd8b5078fa319151b9382258fb0", 325 | "server_mac_key": "0f82432fbdb5b90daf27a91a3acc42299a9590dba1b77932c2207b4cb3d4a157" 326 | }, 327 | "outputs": { 328 | "KE1": "037342f0bcb3ecea754c1e67576c86aa90c1de3875f390ad599a26686cdfee6e07ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1022ed3f32f318f81bab80da321fecab3cd9b6eea11a95666dfa6beeaab321280b6", 329 | "KE2": "0246da9fe4d41d5ba69faa6c509a1d5bafd49a48615a47a8dd4b0823cc1476481138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d2f0c547f70deaeca54d878c14c1aa5e1ab405dec833777132eea905c2fbb12504a67dcbe0e66740c76b62c13b04a38a77926e19072953319ec65e41f9bfd2ae268d7f106042021c80300e4c6f585980cf39fc51a4a6bba41b0729f9b240c729e5671cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a103c1701353219b53acf337bf6456a83cefed8f563f1040b65afbf3b65d3bc9a19b84922c7e5d074838a8f278592c53f61fb59f031e85ad480c0c71086b871e1b24", 330 | "KE3": "46833578cee137775f6be3f01b80748daac5a694101ad0e9e7025480552da56a", 331 | "export_key": "c3c9a1b0e33ac84dd83d0b7e8af6794e17e7a3caadff289fbd9dc769a853c64b", 332 | "registration_request": "029e949a29cfa0bf7c1287333d2fb3dc586c41aa652f5070d26a5315a1b50229f8", 333 | "registration_response": "0350d3694c00978f00a5ce7cd08a00547e4ab5fb5fc2b2f6717cdaa6c89136efef035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9f2092d6067784874", 334 | "registration_upload": "03b218507d978c3db570ca994aaf36695a731ddb2db272c817f79746fc37ae52147f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea029148b2803aafa921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf65670e51f4d7773a36a208a866301dbb2858e40dc5638017527cf91aef32d3848eebe0971", 335 | "session_key": "27766fabd8dd88ff37fbd0ef1a491e601d10d9f016c2b28c4bd1b0fb7511a3c3" 336 | } 337 | }, 338 | { 339 | "config": { 340 | "Context": "4f50415155452d504f43", 341 | "Fake": "True", 342 | "Group": "ristretto255", 343 | "Hash": "SHA512", 344 | "KDF": "HKDF-SHA512", 345 | "KSF": "Identity", 346 | "MAC": "HMAC-SHA512", 347 | "Name": "3DH", 348 | "Nh": "64", 349 | "Nm": "64", 350 | "Nok": "32", 351 | "Npk": "32", 352 | "Nsk": "32", 353 | "Nx": "64", 354 | "OPRF": "ristretto255-SHA512" 355 | }, 356 | "inputs": { 357 | "KE1": "b0a26dcaca2230b8f5e4b1bcab9c84b586140221bb8b2848486874b0be44890542d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0ab641d7f20a245a09f1d4dbb6e301661af7f352beb0791d055e48d3645232f77f", 358 | "client_identity": "616c696365", 359 | "client_keyshare_seed": "a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276d1e15bdeb4c355e94", 360 | "client_private_key": "2b98980aa95ab53a0f39f0291903d2fdf04b00c167f0814169922df873002409", 361 | "client_public_key": "84f43f9492e19c22d8bdaa4447cc3d4db1cdb5427a9f852c4707921212c36251", 362 | "credential_identifier": "31323334", 363 | "masking_key": "39ebd51f0e39a07a1c2d2431995b0399bca9996c5d10014d6ebab4453dc10ce5cef38ed3df6e56bfff40c2d8dd4671c2b4cf63c3d54860f31fe40220d690bb71", 364 | "masking_nonce": "9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a6", 365 | "oprf_seed": "743fc168d1f826ad43738933e5adb23da6fb95f95a1b069f0daa0522d0a78b617f701fc6aa46d3e7981e70de7765dfcd6b1e13e3369a582eb8dc456b10aa53b0", 366 | "server_identity": "626f62", 367 | "server_keyshare_seed": "360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc5b4f6b62df07f78c2", 368 | "server_nonce": "1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813055ae2d12", 369 | "server_private_key": "c788585ae8b5ba2942b693b849be0c0426384e41977c18d2e81fbe30fd7c9f06", 370 | "server_public_key": "825f832667480f08b0c9069da5083ac4d0e9ee31b49c4e0310031fea04d52966" 371 | }, 372 | "intermediates": {}, 373 | "outputs": { 374 | "KE2": "928f79ad8df21963e91411b9f55165ba833dea918f441db967cdc09521d229259c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a632b5ab1bff96636144faa4f9f9afaac75dd88ea99cf5175902ae3f3b2195693f165f11929ba510a5978e64dcdabecbd7ee1e4380ce270e58fea58e6462d92964a1aaef72698bca1c673baeb04cc2bf7de5f3c2f5553464552d3a0f7698a9ca7f9c5e70c6cb1f706b2f175ab9d04bbd13926e816b6811a50b4aafa9799d5ed7971e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813055ae2d1298251c5ba55f6b0b2d58d9ff0c88fe4176484be62a96db6e2a8c4d431bd1bf27fe6c1d0537603835217d42ebf7b2581982732e74892fd28211b31ed33863f0beaf75ba6f59474c0aaf9d78a60a9b2f4cd24d7ab54131b3c8efa192df6b72db4c" 375 | } 376 | }, 377 | { 378 | "config": { 379 | "Context": "4f50415155452d504f43", 380 | "Fake": "True", 381 | "Group": "curve25519", 382 | "Hash": "SHA512", 383 | "KDF": "HKDF-SHA512", 384 | "KSF": "Identity", 385 | "MAC": "HMAC-SHA512", 386 | "Name": "3DH", 387 | "Nh": "64", 388 | "Nm": "64", 389 | "Nok": "32", 390 | "Npk": "32", 391 | "Nsk": "32", 392 | "Nx": "64", 393 | "OPRF": "ristretto255-SHA512" 394 | }, 395 | "inputs": { 396 | "KE1": "b0a26dcaca2230b8f5e4b1bcab9c84b586140221bb8b2848486874b0be44890542d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0ac059b7ba2aec863933ae48816360c7a9022e83d822704f3b0b86c0502a66e574", 397 | "client_identity": "616c696365", 398 | "client_keyshare_seed": "a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276d1e15bdeb4c355e94", 399 | "client_private_key": "288bf63470199221847bb035d99f96531adf8badd14cb1571b48f7a506649660", 400 | "client_public_key": "3c64a3153854cc9f0c23aab3c1a19106ec8bab4730736d1d003880a1d5a59005", 401 | "credential_identifier": "31323334", 402 | "masking_key": "79ad2621b0757a447dff7108a8ae20a068ce67872095620f415ea611c9dcc04972fa359538cd2fd6528775ca775487b2b56db642049b8a90526b975a38484c6a", 403 | "masking_nonce": "9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a6", 404 | "oprf_seed": "66e650652a8266b2205f31fdd68adeb739a05b5e650b19e7edc75e734a1296d6088188ca46c31ae8ccbd42a52ed338c06e53645387a7efbc94b6a0449526155e", 405 | "server_identity": "626f62", 406 | "server_keyshare_seed": "360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc5b4f6b62df07f78c2", 407 | "server_nonce": "1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813055ae2d12", 408 | "server_private_key": "30fbe7e830be1fe8d2187c97414e3826040cbe49b893b64229bab5e85a588846", 409 | "server_public_key": "78b3040047ff26572a7619617601a61b9c81899bee92f00cfcaa5eed96863555" 410 | }, 411 | "intermediates": {}, 412 | "outputs": { 413 | "KE2": "6606b6fedbb33f19a81a1feb5149c600fe77252f58acd3080d7504d3dad4922f9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a67db398c0f65d8c298eac430abdae4c80e82b552fb940c00f0cbcea853c0f96c1c15099f3d4b0e83ecc249613116d605b8d77bb68bdf76994c2bc507e2dcae4176f00afed68ad25cf3040a0e991acece31ca532117f5c12816997372ff031ad04ebcdce06c501da24e7b4db95343456e2ed260895ec362694230a1fa20e24a9c71e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813055ae2d122d9055eb8f83e1b497370adad5cc2a417bf9be436a792def0c7b7ccb92b9e275d7c663104ea4655bd70570d975c05351655d55fbfb392286edb55600a23b55ce18f8c60e0d1960c960412dd08eabc81ba7ca8ae2b04aad65462321f51c298010" 414 | } 415 | }, 416 | { 417 | "config": { 418 | "Context": "4f50415155452d504f43", 419 | "Fake": "True", 420 | "Group": "P256_XMD:SHA-256_SSWU_RO_", 421 | "Hash": "SHA256", 422 | "KDF": "HKDF-SHA256", 423 | "KSF": "Identity", 424 | "MAC": "HMAC-SHA256", 425 | "Name": "3DH", 426 | "Nh": "32", 427 | "Nm": "32", 428 | "Nok": "32", 429 | "Npk": "33", 430 | "Nsk": "32", 431 | "Nx": "32", 432 | "OPRF": "P256-SHA256" 433 | }, 434 | "inputs": { 435 | "KE1": "0396875da2b4f7749bba411513aea02dc514a48d169d8a9531bd61d3af3fa9baae42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0a02147a6583983cc9973b5082db5f5070890cb373d70f7ac1b41ed2305361009784", 436 | "client_identity": "616c696365", 437 | "client_keyshare_seed": "a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276d1e15bdeb4c355e94", 438 | "client_private_key": "d423b87899fc61d014fc8330a4e26190fcfa470a3afe5924324294af7dbbc1dd", 439 | "client_public_key": "03b81708eae026a9370616c22e1e8542fe9dbebd36ce8a2661b708e9628f4a57fc", 440 | "credential_identifier": "31323334", 441 | "masking_key": "caecc6ccb4cae27cb54d8f3a1af1bac52a3d53107ce08497cdd362b1992e4e5e", 442 | "masking_nonce": "9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a6", 443 | "oprf_seed": "bb1cd59e16ac09bc0cb6d528541695d7eba2239b1613a3db3ade77b36280f725", 444 | "server_identity": "626f62", 445 | "server_keyshare_seed": "360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc5b4f6b62df07f78c2", 446 | "server_nonce": "1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813055ae2d12", 447 | "server_private_key": "34fbe7e830be1fe8d2187c97414e3826040cbe49b893b64229bab5e85a5888c7", 448 | "server_public_key": "0221e034c0e202fe883dcfc96802a7624166fed4cfcab4ae30cf5f3290d01c88bf" 449 | }, 450 | "intermediates": {}, 451 | "outputs": { 452 | "KE2": "0201198dcd13f9792eb75dcfa815f61b049abfe2e3e9456d4bbbceec5f442efd049c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a6facda65ce0a97b9085e7af07f61fd3fdd046d257cbf2183ce8766090b8041a8bf28d79dd4c9031ddc75bb6ddb4c291e639937840e3d39fc0d5a3d6e7723c09f7945df485bcf9aefe3fe82d149e84049e259bb5b33d6a2ff3b25e4bfb7eff0962821e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813055ae2d12023f82bbb24e75b8683fd13b843cd566efae996cd0016cffdcc24ee2bc937d026f80144878749a69565b433c1040aff67e94f79345de888a877422b9bbe21ec329" 453 | } 454 | } 455 | ] --------------------------------------------------------------------------------