[GH-ISSUE #1407] Creating config file for Amnezia WG app from Amenzia VPN 'vpn://...' string #582

Closed
opened 2026-10-13 20:24:31 +00:00 by bsakaguchi · 4 comments
Owner

Originally created by @astafan8 on GitHub (Feb 7, 2025).
Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/1407

Since Amnezia VPN iOS app is blocked on Russian App Store, while Amnezia WG somehow isn't (or I just managed to have it installed on my phone before it got blocked) I decided to try and make Amnezia WG app work. Unfortunately, Amnezia WG app does not take Amnezia VPN configuration, neither QR code, nor a .vpn file with vpn://... string. So I decided to figure out how to turn those into a file that Amenzia WG would take. Thanks to the open source code of Amenzia, I was able to find my way, and below I offer you all a python script that does the conversion. With this script, given a .vpn file from someone from runs an Amnezia server, one can turn it into a .conf file that Amnezia WG app can load, making the configuration and setup very easy.

If I am doing something useful here (and not stupid), I would love to contribute this script somewhere where others can make use of this - so let me know what's the best way to do this.

"""
Converts Amnezia VPN 'vpn://...' configuration to WireGuard
configuration file that Amnezia WG app can load.

Execute like this in a terminal:

>>> python -m amnezia_vpn_to_wg_config.py amnezia_config.vpn
"""

import argparse
import base64
import configparser
import io
import json
from pathlib import Path
import zlib


def from_base64_urlsafe(s: str) -> bytes:
    # Link to StackOverflow answer on how to decode Base64 URL-Safe
    # https://stackoverflow.com/questions/3302946/how-to-decode-base64-url-in-python/9956217#9956217
    return base64.urlsafe_b64decode(s + "=" * (4 - len(s) % 4))


def zlib_decompress_from_qcompress(s: bytes) -> bytes:
    # Link to documentation of qUncompress Qt function
    # https://doc.qt.io/qt-6/qbytearray.html#qUncompress
    # https://doc.qt.io/qtforpython-6/PySide6/QtCore/QtCore_globals.html#PySide6.QtCore.qUncompress
    # Link to an answer that refers to qUncompress function docs and explains
    # that the first 4 bytes need to be skipped if one is uncompressing the data
    # outside of Qt functions
    # https://forum.qt.io/topic/123304/uncompressing-data-python-which-is-compressed-using-qt-quncompress-function/2
    # https://forum.qt.io/post/641554
    s_without_first_four_bytes = s[4:]
    return zlib.decompress(s_without_first_four_bytes)


def extract_data_from_vpn_string(vpn_string: str) -> str:
    # Link to “vpn://...” string decompression code in Amnesia GitHub repository
    # https://github.com/amnezia-vpn/amnezia-client/blob/703b9137e0e903b5b9e8c2de2c123ba98195a859/client/ui/controllers/importController.cpp#L153-L154
    encoded_data = vpn_string.strip("vpn://")
    compressed_data = from_base64_urlsafe(encoded_data)
    decompressed_bytes = zlib_decompress_from_qcompress(compressed_data)
    decompressed_data = decompressed_bytes.decode()
    return decompressed_data


def make_wireguard_config_from_amnezia_config(amnezia_config_json: str) -> str:
    amnezia_config = json.loads(amnezia_config_json)

    last_config_json = amnezia_config["containers"][-1]["awg"]["last_config"]
    last_config = json.loads(last_config_json)
    wireguard_config = last_config["config"]

    # WireGuard config is INI formatted

    # Below we are going to add a few modifications to make it
    # look exactly as Amnezia WG app creates it upon export

    wireguard_config = wireguard_config.replace("$PRIMARY_DNS", amnezia_config["dns1"])
    wireguard_config = wireguard_config.replace(
        "$SECONDARY_DNS", amnezia_config["dns2"]
    )

    config = configparser.ConfigParser()
    # Avoid normalizing section names to lowercase by changing
    # ``optionxform`` as documented here
    # https://docs.python.org/3/library/configparser.html#configparser.ConfigParser.optionxform
    config.optionxform = lambda optionstr: optionstr
    config.read_string(wireguard_config)

    config["Interface"]["MTU"] = last_config["mtu"]
    config["Interface"]["ListenPort"] = f"{last_config['port']}"

    with io.StringIO() as final_wireguard_config_string:
        config.write(final_wireguard_config_string)
        final_wireguard_config = final_wireguard_config_string.getvalue()

    return final_wireguard_config


def run(vpn_file: Path) -> Path:
    vpn_file_content = vpn_file.read_text()

    amnezia_config_json = extract_data_from_vpn_string(vpn_file_content)
    wireguard_config = make_wireguard_config_from_amnezia_config(amnezia_config_json)

    wireguard_config_file = vpn_file.with_suffix(".conf")
    wireguard_config_file.write_text(wireguard_config)
    return wireguard_config_file


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description=(
            "Converter from Amnezia VPN 'vpn://...' file to "
            "Amnezia WG (WireGuard) configuration '.conf' file"
        ),
        add_help=True,
    )
    parser.add_argument(
        "vpn_file",
        type=Path,
        help="Amnezia VPN file (usually '.vpn') with 'vpn://...' string",
    )

    args = parser.parse_args()

    resulting_file = run(
        vpn_file=args.vpn_file,
    )
    print(f"Wrote Amnezia WG config to: {resulting_file}")

Originally created by @astafan8 on GitHub (Feb 7, 2025). Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/1407 Since Amnezia VPN iOS app is blocked on Russian App Store, while Amnezia WG somehow isn't (or I just managed to have it installed on my phone before it got blocked) I decided to try and make Amnezia WG app work. Unfortunately, Amnezia WG app does not take Amnezia VPN configuration, neither QR code, nor a `.vpn` file with `vpn://...` string. So I decided to figure out how to turn those into a file that Amenzia WG would take. Thanks to the open source code of Amenzia, I was able to find my way, and below I offer you all a python script that does the conversion. With this script, given a `.vpn` file from someone from runs an Amnezia server, one can turn it into a `.conf` file that Amnezia WG app can load, making the configuration and setup very easy. If I am doing something useful here (and not stupid), I would love to contribute this script somewhere where others can make use of this - so let me know what's the best way to do this. ```python """ Converts Amnezia VPN 'vpn://...' configuration to WireGuard configuration file that Amnezia WG app can load. Execute like this in a terminal: >>> python -m amnezia_vpn_to_wg_config.py amnezia_config.vpn """ import argparse import base64 import configparser import io import json from pathlib import Path import zlib def from_base64_urlsafe(s: str) -> bytes: # Link to StackOverflow answer on how to decode Base64 URL-Safe # https://stackoverflow.com/questions/3302946/how-to-decode-base64-url-in-python/9956217#9956217 return base64.urlsafe_b64decode(s + "=" * (4 - len(s) % 4)) def zlib_decompress_from_qcompress(s: bytes) -> bytes: # Link to documentation of qUncompress Qt function # https://doc.qt.io/qt-6/qbytearray.html#qUncompress # https://doc.qt.io/qtforpython-6/PySide6/QtCore/QtCore_globals.html#PySide6.QtCore.qUncompress # Link to an answer that refers to qUncompress function docs and explains # that the first 4 bytes need to be skipped if one is uncompressing the data # outside of Qt functions # https://forum.qt.io/topic/123304/uncompressing-data-python-which-is-compressed-using-qt-quncompress-function/2 # https://forum.qt.io/post/641554 s_without_first_four_bytes = s[4:] return zlib.decompress(s_without_first_four_bytes) def extract_data_from_vpn_string(vpn_string: str) -> str: # Link to “vpn://...” string decompression code in Amnesia GitHub repository # https://github.com/amnezia-vpn/amnezia-client/blob/703b9137e0e903b5b9e8c2de2c123ba98195a859/client/ui/controllers/importController.cpp#L153-L154 encoded_data = vpn_string.strip("vpn://") compressed_data = from_base64_urlsafe(encoded_data) decompressed_bytes = zlib_decompress_from_qcompress(compressed_data) decompressed_data = decompressed_bytes.decode() return decompressed_data def make_wireguard_config_from_amnezia_config(amnezia_config_json: str) -> str: amnezia_config = json.loads(amnezia_config_json) last_config_json = amnezia_config["containers"][-1]["awg"]["last_config"] last_config = json.loads(last_config_json) wireguard_config = last_config["config"] # WireGuard config is INI formatted # Below we are going to add a few modifications to make it # look exactly as Amnezia WG app creates it upon export wireguard_config = wireguard_config.replace("$PRIMARY_DNS", amnezia_config["dns1"]) wireguard_config = wireguard_config.replace( "$SECONDARY_DNS", amnezia_config["dns2"] ) config = configparser.ConfigParser() # Avoid normalizing section names to lowercase by changing # ``optionxform`` as documented here # https://docs.python.org/3/library/configparser.html#configparser.ConfigParser.optionxform config.optionxform = lambda optionstr: optionstr config.read_string(wireguard_config) config["Interface"]["MTU"] = last_config["mtu"] config["Interface"]["ListenPort"] = f"{last_config['port']}" with io.StringIO() as final_wireguard_config_string: config.write(final_wireguard_config_string) final_wireguard_config = final_wireguard_config_string.getvalue() return final_wireguard_config def run(vpn_file: Path) -> Path: vpn_file_content = vpn_file.read_text() amnezia_config_json = extract_data_from_vpn_string(vpn_file_content) wireguard_config = make_wireguard_config_from_amnezia_config(amnezia_config_json) wireguard_config_file = vpn_file.with_suffix(".conf") wireguard_config_file.write_text(wireguard_config) return wireguard_config_file if __name__ == "__main__": parser = argparse.ArgumentParser( description=( "Converter from Amnezia VPN 'vpn://...' file to " "Amnezia WG (WireGuard) configuration '.conf' file" ), add_help=True, ) parser.add_argument( "vpn_file", type=Path, help="Amnezia VPN file (usually '.vpn') with 'vpn://...' string", ) args = parser.parse_args() resulting_file = run( vpn_file=args.vpn_file, ) print(f"Wrote Amnezia WG config to: {resulting_file}") ```
Author
Owner

@Nethius commented on GitHub (Feb 8, 2025):

  1. On the “Share” page in the AmneziaVPN app, you can select the “AmneziaWG native format” config format and get a .conf file as output.
  2. There are already a dozen python converters, and we have c++ impl in this repo https://github.com/amnezia-vpn/config-decoder
<!-- gh-comment-id:2644445293 --> @Nethius commented on GitHub (Feb 8, 2025): 1) On the “Share” page in the AmneziaVPN app, you can select the “AmneziaWG native format” config format and get a .conf file as output. 2) There are already a dozen python converters, and we have c++ impl in this repo https://github.com/amnezia-vpn/config-decoder
Author
Owner

@astafan8 commented on GitHub (Feb 10, 2025):

@Nethius thank you for both pointers! This is very helpful! Then i'll close the issue.

<!-- gh-comment-id:2647329116 --> @astafan8 commented on GitHub (Feb 10, 2025): @Nethius thank you for both pointers! This is very helpful! Then i'll close the issue.
Author
Owner

@steqtk commented on GitHub (Oct 14, 2025):

У меня так sudo awg addconf wg0 amnezia_vpn.conf выдает
Line unrecognized: Address=10.8.1.26/32'
Configuration parsing error
`
сам конфиг полученный из vpn://... выглядит нормально

<!-- gh-comment-id:3402631453 --> @steqtk commented on GitHub (Oct 14, 2025): У меня так `sudo awg addconf wg0 amnezia_vpn.conf` выдает `Line unrecognized: `Address=10.8.1.26/32' Configuration parsing error ` сам конфиг полученный из vpn://... выглядит нормально
Author
Owner

@J0NGLES commented on GitHub (Nov 8, 2025):

@astafan8

Привет, поможешь мне настроить свой скрипт? У меня нет каких-либо навыков работы с Python, и мне не подходит способ, предложенный выше Nethius-ом. Во всех моих клиентах Amnezia отсутствует кнопка "Поделиться". Попробовал поставить config-decoder, но он VPN-ключи превращает в какой-то совсем другой формат, отличный от .conf-файлов. Очень нужна помощь. Мой провайдер блокирует работу клиента Amnezia, хочу попробовать пихнуть конфиг в WireSock. Может с тобой можно связаться через Telegram или VK? Заранее спасибо!

<!-- gh-comment-id:3506230989 --> @J0NGLES commented on GitHub (Nov 8, 2025): @astafan8 Привет, поможешь мне настроить свой скрипт? У меня нет каких-либо навыков работы с Python, и мне не подходит способ, предложенный выше Nethius-ом. Во всех моих клиентах Amnezia отсутствует кнопка "Поделиться". Попробовал поставить [config-decoder](https://github.com/amnezia-vpn/config-decoder), но он VPN-ключи превращает в какой-то совсем другой формат, отличный от .conf-файлов. Очень нужна помощь. Мой провайдер блокирует работу клиента Amnezia, хочу попробовать пихнуть конфиг в WireSock. Может с тобой можно связаться через Telegram или VK? Заранее спасибо!
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#582
No description provided.