VPN SDK logs on user devices are encoded to protect sensitive information and comply with Apple requirements. However, there may be times during development, debugging, or support when you need to decode these logs. This article explains how to set up your environment and run a Python script to decode VPN SDK logs.
Prerequisites
To decode the logs, you will need:
Python 3
PyCryptodome library
Encoded VPN SDK logs from the user's device
Decryption key and initialization vector (obtained from your Pango account representative)
Environment Setup
Install Python 3 using your preferred method. For example, using Homebrew:
brew install python3
Ensure the path for python3 is added to your PATH variable and pip is set to pip3.
Set up your environment with:
python3 -m venv env
. env/bin/activate
Install the PyCryptodome library:
pip install pycryptodome
If you encounter an "externally-managed-environment" error, try:
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
xyz, where xyz is the package you are trying to
install.
If you wish to install a Python library that isn't in Homebrew,
use a virtual environment:
python3 -m venv path/to/venv
source path/to/venv/bin/activate
python3 -m pip install xyz
If you wish to install a Python application that isn't in Homebrew,
it may be easiest to use 'pipx install xyz', which will manage a
virtual environment for you. You can install pipx with
brew install pipx
You may restore the old behavior of pip by passing
the '--break-system-packages' flag to pip, or by adding
'break-system-packages = true' to your pip.conf file. The latter
will permanently disable this error.
If you disable this error, we STRONGLY recommend that you additionally
pass the '--user' flag to pip, or set 'user = true' in your pip.conf
file. Failure to do this can result in a broken Homebrew installation.
Read more about this behavior here: <https://peps.python.org/pep-0668/>
pip install pycryptodome --break-system-packages
Obtaining Encoded Logs
Decoding Logs
Save the decode_logs.py script (provided below) in a convenient folder.
decode_logs.py
import sys, getopt
import os
from Crypto.Cipher import AES
def decode(bytes, key, vector):
key = key.encode()
vector = vector.encode()
cipher = AES.new(key, AES.MODE_CBC, vector)
plain_text = cipher.decrypt(bytes)
decoded = plain_text.decode()
text, *sep = decoded.partition('\n')
return text + sep[0]
def decodeFile(inputFile, outputPath, decodingKey, decodingVector):
decoded_content = ''
with open(inputFile, 'rb') as f:
while True:
header = f.read(13)
line_length = header[5:]
if line_length == b'':
break
file_size = int.from_bytes(line_length, byteorder='little')
content = f.read(file_size)
decoded_content = decoded_content + decode(content, decodingKey, decodingVector)
if outputPath is not None:
if not os.path.exists(outputPath):
os.mkdir(outputPath)
outputFilePath = os.path.join(outputPath, os.path.basename(inputFile) + '_decoded')
with open(outputFilePath, 'w') as outputfile:
outputfile.write(decoded_content)
outputfile.close()
else:
print(decoded_content)
def printHelp():
print("""Help:
-----------------------------------------------
Script is used to decode VPNSDK encoded logs.
-----------------------------------------------
Arguments:
-i path to encoded file or directory with logs
-o output path for decoded logs
-k decoding key to be used in logs decoding
-v decoding initialization vector to be used in logs decoding
-----------------------------------------------
Example:
decode_logs.py -i Hydra-Logs -o ./decoded_logs -k 'YOUR_DECODING_KEY' -v 'YOUR_DECODING_VECTOR'""")
def main(argv):
inputPath = ''
outputPath = None
decodingKey = ''
decodingVector = ''
opts, args = getopt.getopt(argv,"i:o:h:k:v:")
for opt, arg in opts:
if opt == '-h':
printHelp()
sys.exit()
elif opt in ("-i", "--input"):
inputPath = arg
elif opt in ("-o", "--output"):
outputPath = arg
elif opt in ("-k", "--key"):
decodingKey = arg
elif opt in ("-v", "--vector"):
decodingVector = arg
if os.path.isdir(inputPath):
for file in os.scandir(inputPath):
if not file.name.startswith('.'):
decodeFile(file.path, outputPath, decodingKey, decodingVector)
print(f'✅ {file.name}')
elif os.path.isfile(inputPath):
decodeFile(inputPath, outputPath, decodingKey, decodingVector)
else:
printHelp()
sys.exit()
if __name__ == "__main__":
main(sys.argv[1:])
Move the encoded logs to a separate folder containing no other files. This can be a subfolder of the script's location.
Create an output folder for the decoded logs. The decoded files will have a "_decrypted" suffix added to their names.
In the Terminal, navigate to the script's folder and run it with the following parameters:
Replace the placeholders with the appropriate paths and decryption parameters.
If successful, you will see output like:
✅ vpnsdk_log
Adding Log Encoding Credentials
Starting from version 6.8.0 of Unified VPN SDK for Apple, it is possible to add CryptographicCredentials for Hydra and WireGuard configurations, as well as Combined configurations. These credentials are used in the process of encoding logs for secure transmission and later decoding them for analysis.
To add custom CryptographicCredentials, you need to create an instance of the CryptographicCredentials class, providing the necessary key and iv values. Then, pass this instance to the logCryptographicCredentials: CryptographicCredentials? property of your configuration:
let logCryptographicCredentials = CryptographicCredentials(
key: "YOUR_CRYPTOGRAPHIC_KEY",
iv: "YOUR_CRYPTOGRAPHIC_IV")
let validatedLogCryptographicCredentials = logCryptographicCredentials.isValid ?
logCryptographicCredentials : nil
var hydraConfiguration = HydraConfiguration(
// ...
logCryptographicCredentials: validatedLogCryptographicCredentials,
// ...
)
It is important to note that the logCryptographicCredentials property is optional in any configuration and can be omitted. If no custom credentials are provided (i.e., the property is set to nil), default credentials will be used for log encoding. In this case, the encoded logs can only be decoded by the Apple VPN SDK Team upon request from the Partner's development team.
To ensure the validity of the provided CryptographicCredentials, it is recommended to check the isValid property before assigning the credentials to the logCryptographicCredentials property. If the credentials are invalid, you can set the property to nil to use the default credentials.
Troubleshooting
When executing the decode_logs.py script, the following error traceback is observed:
Traceback (most recent call last):
File "../Scripts/decode_logs.py", line 79, in <module>
main(sys.argv[1:])
~~~~^^^^^^^^^^^^^^
File "../Scripts/decode_logs.py", line 70, in main
decodeFile(file.path, outputPath, decodingKey, decodingVector)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "../Scripts/decode_logs.py", line 16, in decodeFile
with open(inputFile, 'rb') as f:
~~~~^^^^^^^^^^^^^^^^^
IsADirectoryError: [Errno 21] Is a directory: '../logs/decoded'
The error indicates that the script is attempting to open a directory ('../logs/decoded') as a file, resulting in an "IsADirectoryError".
To resolve the "IsADirectoryError", follow these steps:
Locate the logs source folder (../logs/). Identify the decoded logs output folder (../logs/decoded/) within the logs source folder.
Move the decoded logs output folder outside of the logs source folder to a separate location.
Update the decode_logs.py script to point to the new location of the decoded logs output folder.
Re-run the decode_logs.py script to verify that the error has been resolved.
The user's device stores logs for the 10 latest VPN sessions. Refer to for detailed instructions on enabling debug logging, obtaining logs, and real-time debugging.