Skip to content

Instantly share code, notes, and snippets.

@4skl
Last active November 12, 2025 09:12
Show Gist options
  • Select an option

  • Save 4skl/7df45d630a734af4d4fc209587e4fad9 to your computer and use it in GitHub Desktop.

Select an option

Save 4skl/7df45d630a734af4d4fc209587e4fad9 to your computer and use it in GitHub Desktop.
Bluetooth Pairing Key Sync Tool - Dual Boot Windows/Linux

Bluetooth Pairing Key Sync Tool

Sync Bluetooth pairing keys between Windows and Linux dual-boot systems.

What It Does

Eliminates the need to re-pair Bluetooth devices when switching between Windows and Linux. The script extracts the pairing key from Windows and applies it to Linux, so your devices work seamlessly on both operating systems.

Requirements

  • Dual-boot system (Windows + Linux)
  • Bluetooth device paired on both systems at least once
  • Sudo access
  • Bitlocker disabled for the operation

Usage

chmod +x bt-key-sync.sh
./bt-key-sync.sh
#!/bin/bash
# Bluetooth Pairing Key Sync Script (Windows <-> Linux)
# This script helps you sync Bluetooth device pairing keys between Windows and Linux
#
# Supported distributions: Ubuntu, Debian, Fedora, Arch, openSUSE, and derivatives
#
# Features:
# - Automatic detection and selection of Bluetooth adapters and devices
# - Automatic detection of Windows/NTFS partitions with mount status
# - Automatic extraction of pairing keys from Windows registry
# - Smart mounting (detects already-mounted partitions)
# - Automatic backup of existing Bluetooth configuration
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored messages
print_info() {
echo -e "${BLUE}ℹ ${NC}$1"
}
print_success() {
echo -e "${GREEN}✓${NC} $1"
}
print_warning() {
echo -e "${YELLOW}⚠${NC} $1"
}
print_error() {
echo -e "${RED}✗${NC} $1"
}
print_header() {
echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} $1${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}\n"
}
# Function to pause and wait for user
pause() {
echo ""
read -p "Press Enter to continue..."
echo ""
}
# Function to cleanup mount if we created it
cleanup_mount() {
if [ "$SHOULD_UNMOUNT" = true ] && [ -n "$MOUNT_POINT" ]; then
sudo umount "$MOUNT_POINT" 2>/dev/null || true
sudo rmdir "$MOUNT_POINT" 2>/dev/null || true
fi
}
# Function to detect Linux distribution
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
DISTRO_ID="$ID"
DISTRO_LIKE="$ID_LIKE"
elif [ -f /etc/lsb-release ]; then
. /etc/lsb-release
DISTRO_ID="$DISTRIB_ID"
else
DISTRO_ID="unknown"
fi
# Convert to lowercase
DISTRO_ID=$(echo "$DISTRO_ID" | tr '[:upper:]' '[:lower:]')
DISTRO_LIKE=$(echo "$DISTRO_LIKE" | tr '[:upper:]' '[:lower:]')
}
# Function to install chntpw based on distribution
install_chntpw() {
case "$DISTRO_ID" in
fedora|rhel|centos|rocky|alma)
print_info "Detected Red Hat-based distribution. Using dnf/yum..."
if command -v dnf &> /dev/null; then
sudo dnf install -y chntpw
else
sudo yum install -y chntpw
fi
;;
ubuntu|debian|linuxmint|pop|elementary)
print_info "Detected Debian-based distribution. Using apt..."
sudo apt update
sudo apt install -y chntpw
;;
arch|manjaro|endeavouros|garuda)
print_info "Detected Arch-based distribution. Using pacman..."
sudo pacman -Sy --noconfirm chntpw
;;
opensuse*|suse|sles)
print_info "Detected SUSE-based distribution. Using zypper..."
sudo zypper install -y chntpw
;;
gentoo)
print_info "Detected Gentoo. Using emerge..."
sudo emerge -av app-admin/chntpw
;;
*)
# Check ID_LIKE for derivative distributions
if [[ "$DISTRO_LIKE" == *"debian"* ]] || [[ "$DISTRO_LIKE" == *"ubuntu"* ]]; then
print_info "Detected Debian-like distribution. Using apt..."
sudo apt update
sudo apt install -y chntpw
elif [[ "$DISTRO_LIKE" == *"fedora"* ]] || [[ "$DISTRO_LIKE" == *"rhel"* ]]; then
print_info "Detected Red Hat-like distribution. Using dnf/yum..."
if command -v dnf &> /dev/null; then
sudo dnf install -y chntpw
else
sudo yum install -y chntpw
fi
elif [[ "$DISTRO_LIKE" == *"arch"* ]]; then
print_info "Detected Arch-like distribution. Using pacman..."
sudo pacman -Sy --noconfirm chntpw
elif [[ "$DISTRO_LIKE" == *"suse"* ]]; then
print_info "Detected SUSE-like distribution. Using zypper..."
sudo zypper install -y chntpw
else
print_error "Unsupported distribution: $DISTRO_ID"
print_warning "Please install 'chntpw' manually using your package manager."
return 1
fi
;;
esac
}
# Check if running as root
if [ "$EUID" -eq 0 ]; then
print_error "Please do NOT run this script as root. It will ask for sudo when needed."
exit 1
fi
clear
echo -e "${GREEN}"
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ Bluetooth Pairing Key Sync Tool ║"
echo "║ Windows <-> Linux ║"
echo "║ ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
print_info "This script will help you sync Bluetooth pairing keys between Windows and Linux."
print_info "This allows your Bluetooth devices to work seamlessly on both operating systems."
# Detect distribution
detect_distro
print_info "Detected distribution: $DISTRO_ID"
pause
# ==================== PHASE 1: PREREQUISITES ====================
print_header "PHASE 1: Prerequisites & Setup"
# Check if chntpw is installed
print_info "Checking if 'chntpw' is installed..."
if ! command -v chntpw &> /dev/null; then
print_warning "'chntpw' is not installed."
read -p "Would you like to install it now? (y/n): " install_choice
if [[ $install_choice =~ ^[Yy]$ ]]; then
print_info "Installing chntpw..."
if install_chntpw; then
print_success "'chntpw' installed successfully!"
else
print_error "Failed to install 'chntpw'. Please install it manually."
exit 1
fi
else
print_error "Cannot proceed without 'chntpw'. Exiting."
exit 1
fi
else
print_success "'chntpw' is already installed."
fi
pause
# ==================== PHASE 2: IDENTIFY MAC ADDRESSES ====================
print_header "PHASE 2: Identify MAC Addresses"
print_info "Detecting Bluetooth adapters..."
echo ""
# Get list of Bluetooth adapters
mapfile -t ADAPTERS < <(bluetoothctl list | grep -oE '[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}')
mapfile -t ADAPTER_NAMES < <(bluetoothctl list | sed 's/Controller [0-9A-F:]\+ //')
if [ ${#ADAPTERS[@]} -eq 0 ]; then
print_error "No Bluetooth adapters found!"
exit 1
elif [ ${#ADAPTERS[@]} -eq 1 ]; then
ADAPTER_MAC="${ADAPTERS[0]}"
print_success "Found 1 Bluetooth adapter: $ADAPTER_MAC (${ADAPTER_NAMES[0]})"
else
bluetoothctl list
echo ""
print_info "Found ${#ADAPTERS[@]} Bluetooth adapters. Please select one:"
for i in "${!ADAPTERS[@]}"; do
echo " $((i+1)). ${ADAPTERS[$i]} - ${ADAPTER_NAMES[$i]}"
done
echo ""
read -p "Select adapter (1-${#ADAPTERS[@]}): " adapter_choice
ADAPTER_MAC="${ADAPTERS[$((adapter_choice-1))]}"
fi
ADAPTER_MAC_STRIPPED=$(echo "$ADAPTER_MAC" | tr -d ':' | tr '[:upper:]' '[:lower:]')
print_success "Selected Adapter: $ADAPTER_MAC"
print_info "Adapter MAC (stripped): $ADAPTER_MAC_STRIPPED"
echo ""
print_info "Detecting paired Bluetooth devices..."
echo ""
# Get list of Bluetooth devices
mapfile -t DEVICES < <(bluetoothctl devices | grep -oE '[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}')
mapfile -t DEVICE_NAMES < <(bluetoothctl devices | sed 's/Device [0-9A-F:]\+ //')
if [ ${#DEVICES[@]} -eq 0 ]; then
print_error "No paired Bluetooth devices found!"
print_warning "Please pair your device in Linux first, then run this script again."
exit 1
elif [ ${#DEVICES[@]} -eq 1 ]; then
DEVICE_MAC="${DEVICES[0]}"
print_success "Found 1 paired device: $DEVICE_MAC (${DEVICE_NAMES[0]})"
else
bluetoothctl devices
echo ""
print_info "Found ${#DEVICES[@]} paired devices. Please select one:"
for i in "${!DEVICES[@]}"; do
echo " $((i+1)). ${DEVICES[$i]} - ${DEVICE_NAMES[$i]}"
done
echo ""
read -p "Select device (1-${#DEVICES[@]}): " device_choice
DEVICE_MAC="${DEVICES[$((device_choice-1))]}"
fi
DEVICE_MAC_STRIPPED=$(echo "$DEVICE_MAC" | tr -d ':' | tr '[:upper:]' '[:lower:]')
print_success "Selected Device: $DEVICE_MAC"
print_info "Device MAC (stripped): $DEVICE_MAC_STRIPPED"
pause
# ==================== PHASE 3: MOUNT WINDOWS PARTITION ====================
print_header "PHASE 3: Mount Windows Partition"
print_info "Detecting Windows/NTFS partitions..."
echo ""
# Detect NTFS partitions (Windows)
mapfile -t PARTITIONS < <(lsblk -nrpo NAME,FSTYPE,SIZE,LABEL,MOUNTPOINT | grep -E 'ntfs|fuseblk' | awk '{print $1}')
mapfile -t PARTITION_INFO < <(lsblk -nrpo NAME,FSTYPE,SIZE,LABEL,MOUNTPOINT | grep -E 'ntfs|fuseblk' | awk '{print $2" "$3" "$4" "$5}')
if [ ${#PARTITIONS[@]} -eq 0 ]; then
print_error "No NTFS (Windows) partitions found!"
echo ""
print_info "Showing all available partitions:"
lsblk -f
echo ""
read -p "Enter your Windows partition manually (e.g., /dev/sda2): " WINDOWS_PARTITION
if [ ! -b "$WINDOWS_PARTITION" ]; then
print_error "Partition $WINDOWS_PARTITION does not exist!"
exit 1
fi
ALREADY_MOUNTED=""
else
print_info "Found ${#PARTITIONS[@]} Windows partition(s):"
echo ""
for i in "${!PARTITIONS[@]}"; do
IFS=' ' read -r FSTYPE SIZE LABEL MOUNTPOINT <<< "${PARTITION_INFO[$i]}"
MOUNTPOINT_DISPLAY="${MOUNTPOINT:-not mounted}"
if [ -n "$LABEL" ]; then
echo " $((i+1)). ${PARTITIONS[$i]} - $FSTYPE, $SIZE, Label: $LABEL [$MOUNTPOINT_DISPLAY]"
else
echo " $((i+1)). ${PARTITIONS[$i]} - $FSTYPE, $SIZE [$MOUNTPOINT_DISPLAY]"
fi
done
echo ""
if [ ${#PARTITIONS[@]} -eq 1 ]; then
print_info "Only one Windows partition found. Selecting automatically..."
partition_choice=1
else
read -p "Select partition (1-${#PARTITIONS[@]}): " partition_choice
fi
WINDOWS_PARTITION="${PARTITIONS[$((partition_choice-1))]}"
IFS=' ' read -r FSTYPE SIZE LABEL ALREADY_MOUNTED <<< "${PARTITION_INFO[$((partition_choice-1))]}"
fi
print_success "Selected partition: $WINDOWS_PARTITION"
# Check if partition is already mounted
if [ -n "$ALREADY_MOUNTED" ]; then
print_success "Partition is already mounted at: $ALREADY_MOUNTED"
MOUNT_POINT="$ALREADY_MOUNTED"
SHOULD_UNMOUNT=false
else
MOUNT_POINT="/mnt/windows_bluetooth_temp"
SHOULD_UNMOUNT=true
print_info "Creating mount point at $MOUNT_POINT..."
sudo mkdir -p "$MOUNT_POINT"
print_info "Mounting Windows partition..."
if sudo mount -o ro "$WINDOWS_PARTITION" "$MOUNT_POINT" 2>/dev/null; then
print_success "Windows partition mounted successfully!"
else
print_warning "Failed to mount with default options. Trying with NTFS-3G..."
if sudo mount -t ntfs-3g -o ro "$WINDOWS_PARTITION" "$MOUNT_POINT"; then
print_success "Windows partition mounted successfully with NTFS-3G!"
else
print_error "Failed to mount Windows partition. Exiting."
sudo rmdir "$MOUNT_POINT" 2>/dev/null
exit 1
fi
fi
fi
pause
# ==================== PHASE 4: EXTRACT KEY FROM WINDOWS ====================
print_header "PHASE 4: Extract Bluetooth Key from Windows Registry"
REGISTRY_PATH="$MOUNT_POINT/Windows/System32/config"
if [ ! -f "$REGISTRY_PATH/SYSTEM" ]; then
print_error "Windows SYSTEM registry file not found at $REGISTRY_PATH/SYSTEM"
print_error "Please verify your Windows partition is correctly mounted."
cleanup_mount
exit 1
fi
print_success "Found Windows SYSTEM registry file."
echo ""
print_info "Automatically extracting the Bluetooth pairing key from Windows..."
cd "$REGISTRY_PATH"
# Create a script for chntpw to execute
CHNTPW_SCRIPT=$(mktemp)
cat > "$CHNTPW_SCRIPT" << EOF
cd ControlSet001\\Services\\BTHPORT\\Parameters\\Keys
cd $ADAPTER_MAC_STRIPPED
hex $DEVICE_MAC_STRIPPED
q
EOF
print_info "Running automated registry extraction..."
# Run chntpw and capture output
CHNTPW_OUTPUT=$(sudo chntpw -e SYSTEM < "$CHNTPW_SCRIPT" 2>&1)
rm "$CHNTPW_SCRIPT"
# Extract the hex key from the output
# The 'hex' command outputs 16 bytes in format: XX XX XX XX ... (32 hex chars total)
LINKKEY=$(echo "$CHNTPW_OUTPUT" | grep -oE '([0-9A-Fa-f]{2} ){15}[0-9A-Fa-f]{2}' | head -1 | tr -d ' ' | tr '[:lower:]' '[:upper:]')
# Validate LinkKey format
if [[ ! $LINKKEY =~ ^[0-9A-F]{32}$ ]]; then
print_error "Automatic extraction failed. The key was not found in the expected format."
echo ""
print_warning "Registry output for debugging:"
echo "$CHNTPW_OUTPUT"
echo ""
print_info "Please enter the LinkKey manually."
print_warning "Enter the hex values WITHOUT spaces (e.g., 1A2B3C4D5E6F708192A3B4C5D6E7F809)"
echo ""
print_info "If you need to extract it manually, run:"
echo " cd $REGISTRY_PATH"
echo " sudo chntpw -e SYSTEM"
echo ""
read -p "LinkKey (or press Ctrl+C to exit): " LINKKEY
LINKKEY=$(echo "$LINKKEY" | tr -d ' ' | tr '[:lower:]' '[:upper:]')
if [[ ! $LINKKEY =~ ^[0-9A-F]{32}$ ]]; then
print_error "Invalid LinkKey format! It should be 32 hexadecimal characters."
print_error "Example: 1A2B3C4D5E6F708192A3B4C5D6E7F809"
cleanup_mount
exit 1
fi
fi
print_success "LinkKey extracted successfully: $LINKKEY"
pause
# ==================== PHASE 5: INJECT KEY INTO FEDORA ====================
print_header "PHASE 5: Inject Key into Linux Bluetooth Configuration"
LINUX_BT_PATH="/var/lib/bluetooth/$ADAPTER_MAC/$DEVICE_MAC"
print_info "Checking if device configuration exists..."
if ! sudo test -d "$LINUX_BT_PATH"; then
print_warning "Device configuration directory not found at $LINUX_BT_PATH"
print_info "You may need to pair the device in Linux first, then run this script again."
read -p "Would you like to continue anyway and create the directory? (y/n): " create_choice
if [[ ! $create_choice =~ ^[Yy]$ ]]; then
print_error "Exiting. Please pair the device in Linux first."
cleanup_mount
exit 1
fi
sudo mkdir -p "$LINUX_BT_PATH"
fi
INFO_FILE="$LINUX_BT_PATH/info"
# Optional backup
if sudo test -f "$INFO_FILE"; then
read -p "A Bluetooth configuration file already exists. Create a backup? (y/n): " backup_choice
if [[ $backup_choice =~ ^[Yy]$ ]]; then
print_info "Creating backup of current info file..."
sudo cp "$INFO_FILE" "$INFO_FILE.backup.$(date +%Y%m%d_%H%M%S)"
print_success "Backup created."
else
print_warning "Skipping backup. Existing configuration will be modified."
fi
fi
print_info "Updating LinkKey in $INFO_FILE..."
# Create a temporary file with the updated info
TEMP_FILE=$(mktemp)
if sudo test -f "$INFO_FILE"; then
# Read existing file and update or add LinkKey section
sudo cat "$INFO_FILE" > "$TEMP_FILE" 2>/dev/null || touch "$TEMP_FILE"
# Check if [LinkKey] section exists
if grep -q "^\[LinkKey\]" "$TEMP_FILE"; then
# Update existing LinkKey
sed -i "/^\[LinkKey\]/,/^\[/ s/^Key=.*/Key=$LINKKEY/" "$TEMP_FILE"
print_info "Updated existing [LinkKey] section."
else
# Add new LinkKey section
echo "" >> "$TEMP_FILE"
echo "[LinkKey]" >> "$TEMP_FILE"
echo "Key=$LINKKEY" >> "$TEMP_FILE"
echo "Type=4" >> "$TEMP_FILE"
echo "PINLength=0" >> "$TEMP_FILE"
print_info "Added new [LinkKey] section."
fi
else
# Create new info file
echo "[LinkKey]" > "$TEMP_FILE"
echo "Key=$LINKKEY" >> "$TEMP_FILE"
echo "Type=4" >> "$TEMP_FILE"
echo "PINLength=0" >> "$TEMP_FILE"
print_info "Created new info file with LinkKey."
fi
# Copy temp file to actual location
sudo cp "$TEMP_FILE" "$INFO_FILE"
sudo chown root:root "$INFO_FILE"
sudo chmod 600 "$INFO_FILE"
rm "$TEMP_FILE"
print_success "LinkKey successfully updated in Linux Bluetooth configuration!"
pause
# ==================== PHASE 6: CLEANUP AND RESTART ====================
print_header "PHASE 6: Cleanup and Restart Bluetooth Service"
if [ "$SHOULD_UNMOUNT" = true ]; then
print_info "Unmounting Windows partition..."
sudo umount "$MOUNT_POINT"
sudo rmdir "$MOUNT_POINT"
print_success "Windows partition unmounted and mount point removed."
else
print_info "Partition was already mounted. Leaving it mounted at $MOUNT_POINT"
fi
print_info "Restarting Bluetooth service..."
sudo systemctl restart bluetooth
print_success "Bluetooth service restarted!"
echo ""
print_header "✓ COMPLETED SUCCESSFULLY!"
echo ""
print_success "Your Bluetooth device should now work on both Windows and Linux!"
print_info "Device: $DEVICE_MAC"
print_info "Adapter: $ADAPTER_MAC"
print_info "LinkKey: $LINKKEY"
echo ""
print_warning "If the device doesn't connect automatically, try:"
echo " 1. Turn off the Bluetooth device"
echo " 2. Turn it back on"
echo " 3. It should connect automatically"
echo ""
if [[ $backup_choice =~ ^[Yy]$ ]]; then
print_info "A backup of the original configuration was saved at:"
print_info " $INFO_FILE.backup.*"
echo ""
fi
exit 0
@4skl
Copy link
Author

4skl commented Nov 10, 2025

Only tested on fedora for now, but should work on other linux system without issues, feel free to comment there if you tested the script on another Linux distro

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment