NX password scrambling and unscrambling algorithms in python 2.7

The NX web page entitled “The password scrambling algorithm in NX client” describes their password scrambling algorithm in C++ and perl but it does not describe it in python. Nor does it describe how to unscramble the data.

This blog describes how I implemented both algorithms in python. I was surprised that NX didn’t use a standard symmetrical key algorithm like blowfish or DES for storing the keys but they must have some good reason.

DOWNLOADS

SCRAMBLING ALGORITHM

The scrambling algorithm is more for visual obfuscation than security as is clearly stated on their web page.

Script Description
nxpasswd.py Scrambling algorithm.
nxpasswd.cc Scrambling algorithm in vanilla C++. The NoMachine example assumes that you are using the Qt library.
nxdecode.py Unscrambling algorithm.
test.sh Test script.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/usr/bin/env python
# ================================================================
# Author : Joe Linoff
# Date : 2012-02-04
# Version: 1.0
#
# Implements the password scrambling algorithm used by NX in python as
# defined here:
#
# http://www.nomachine.com/ar/view.php?ar_id=AR01C00125
# https://gist.github.com/902387
#
# Usage:
#     ./nxpasswd.py import os
# ================================================================
import os
import sys
import string
import time
 
validCharList = [
         "!", "#", "$", "%", "&", "(", ")", "*", "+", "-",
         ".", "0", "1", "2", "3", "4", "5", "6", "7", "8",
         "9", ":", ";", "<", ">", "?", "@", "A", "B", "C",
         "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
         "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
         "X", "Y", "Z", "[", "]", "_", "a", "b", "c", "d",
         "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
         "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",
         "y", "z", "{", "|", "}"
         ]
 
def encodePassword(p):
    sPass = ":"
    sTmp = ""
 
    if p in ['', None]:
        return ''
 
    for i in range(len(p)):
        sPass += '%d:' % (ord(p[i])+i+1)
 
    return sPass
 
def findCharInList(c):
    global validCharList
    for i in range(len(validCharList)):
        if validCharList[i] == c:
            return i
    return -1
 
def getRandomValidCharFromList():
    global validCharList
    lt = time.localtime()
    s = lt.tm_sec
    if os.getenv('NXPASSWD_NONRANDOM'):
        s = int(os.getenv('NXPASSWD_NONRANDOM'))
    return validCharList[s]
 
def URLEncode(url):
    url = url.replace('&','&amp;')
    url = url.replace('"','&quot;')
    url = url.replace("'",'&apos;')
    url = url.replace('<','&lt;')
    url = url.replace('>','&gt;')
    return url
 
def scrambleString(s):
    global validCharList
    dummyString = "{{{{"
    sRet = ''
    
    if s in ['', None]:
        return ''
    
    s1 = encodePassword(s)
    
    if len(s1) < 32:
        sRet += dummyString
        
    sRet += s1[::-1]  # reverse string
    
    if len(sRet) < 32:
        sRet += dummyString
 
    ch = getRandomValidCharFromList()
    k = ord(ch) + len(sRet) - 2
    sRet = ch + sRet
    
    for i in range(1,len(sRet)):
        j = findCharInList(sRet[i])
        if j == -1:
            return sRet
        n = (j + k * (i+1)) % len(validCharList)
        sRet = sRet[:i] + validCharList[n] + sRet[i+1:]
 
    sRet += chr((ord(getRandomValidCharFromList())) + 2)
    sRet = URLEncode(sRet)
    return sRet
 
for i in range(1,len(sys.argv)):
    p=sys.argv[i]
    print scrambleString(p)