{"id":156,"date":"2012-02-06T00:21:45","date_gmt":"2012-02-06T00:21:45","guid":{"rendered":"http:\/\/joelinoff.com\/blog\/?p=156"},"modified":"2026-06-28T17:38:51","modified_gmt":"2026-06-29T00:38:51","slug":"nx-password-scrambling-and-unscrambling-algorithms-python","status":"publish","type":"post","link":"https:\/\/joelinoff.com\/blog\/?p=156","title":{"rendered":"NX password scrambling and unscrambling algorithms in python 2.7"},"content":{"rendered":"The NX web page entitled &#8220;<a title=\"http:\/\/www.nomachine.com\/ar\/view.php?ar_id=AR01C00125\" href=\"http:\/\/www.nomachine.com\/ar\/view.php?ar_id=AR01C00125\"><span><strong>The password scrambling algorithm in NX client<\/strong><\/span><\/a>&#8221; 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.\n\nThis blog describes how I implemented both algorithms in python. I was surprised that NX didn&#8217;t use a standard symmetrical key algorithm like blowfish or DES for storing the keys but they must have some good reason.\n<!--more-->\n<h1>DOWNLOADS<\/h1>\n<table>\n  <thead>\n    <tr>\n      <th>Script<\/th>\n      <th>Description<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody>\n    <tr>\n      <td><a href=\"http:\/\/projects.joelinoff.com\/nx\/nxpasswd.py\" title=\"http:\/\/projects.joelinoff.com\/nx\/nxpasswd.py\" target=\"_blank\">nxpasswd.py<\/a><\/td>\n      <td>Scrambling algorithm.<\/td>\n    <\/tr>\n    <tr>\n      <td><a href=\"http:\/\/projects.joelinoff.com\/nx\/nxpasswd.cc\" title=\"http:\/\/projects.joelinoff.com\/nx\/nxpasswd.cc\" target=\"_blank\">nxpasswd.cc<\/a><\/td>\n      <td>Scrambling algorithm in vanilla C++. The NoMachine example assumes that you are using the Qt library.<\/td>\n    <\/tr>\n    <tr>\n      <td><a href=\"http:\/\/projects.joelinoff.com\/nx\/nxdecode.py\" title=\"http:\/\/projects.joelinoff.com\/nx\/nxdecode.py\" target=\"_blank\">nxdecode.py<\/a><\/td>\n      <td>Unscrambling algorithm.<\/td>\n    <\/tr>\n    <tr>\n      <td><a href=\"http:\/\/projects.joelinoff.com\/nx\/test.sh\" title=\"http:\/\/projects.joelinoff.com\/nx\/test.sh\" target=\"_blank\">test.sh<\/a><\/td>\n      <td>Test script.<\/td>\n    <\/tr>\n  <\/tbody\n<\/table>\n\n<h1>SCRAMBLING ALGORITHM<\/h1>\nThe scrambling algorithm is more for visual obfuscation than security as is clearly stated on their web page. \n\n\n<pre class=\"wp-block-code language-python\"><code class=\"language-python\">#!\/usr\/bin\/env python\n# ================================================================\n# Author : Joe Linoff\n# Date : 2012-02-04\n# Version: 1.0\n#\n# Implements the password scrambling algorithm used by NX in python as\n# defined here:\n#\n# http:\/\/www.nomachine.com\/ar\/view.php?ar_id=AR01C00125\n# https:\/\/gist.github.com\/902387\n#\n# Usage:\n#     .\/nxpasswd.py import os\n# ================================================================\nimport os\nimport sys\nimport string\nimport time\n\nvalidCharList = [\n         \"!\", \"#\", \"$\", \"%\", \"&amp;\", \"(\", \")\", \"*\", \"+\", \"-\",\n         \".\", \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\",\n         \"9\", \":\", \";\", \"&lt;\", \"&gt;\", \"?\", \"@\", \"A\", \"B\", \"C\",\n         \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\", \"K\", \"L\", \"M\",\n         \"N\", \"O\", \"P\", \"Q\", \"R\", \"S\", \"T\", \"U\", \"V\", \"W\",\n         \"X\", \"Y\", \"Z\", \"[\", \"]\", \"_\", \"a\", \"b\", \"c\", \"d\",\n         \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\",\n         \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\",\n         \"y\", \"z\", \"{\", \"|\", \"}\"\n         ]\n\ndef encodePassword(p):\n    sPass = \":\"\n    sTmp = \"\"\n\n    if p in ['', None]:\n        return ''\n\n    for i in range(len(p)):\n        sPass += '%d:' % (ord(p[i])+i+1)\n\n    return sPass\n\ndef findCharInList(c):\n    global validCharList\n    for i in range(len(validCharList)):\n        if validCharList[i] == c:\n            return i\n    return -1\n\ndef getRandomValidCharFromList():\n    global validCharList\n    lt = time.localtime()\n    s = lt.tm_sec\n    if os.getenv('NXPASSWD_NONRANDOM'):\n        s = int(os.getenv('NXPASSWD_NONRANDOM'))\n    return validCharList[s]\n\ndef URLEncode(url):\n    url = url.replace('&amp;','&amp;')\n    url = url.replace('\"','\"')\n    url = url.replace(\"'\",''')\n    url = url.replace('&lt;','&lt;')\n    url = url.replace('&gt;','&gt;')\n    return url\n\ndef scrambleString(s):\n    global validCharList\n    dummyString = \"{{{{\"\n    sRet = ''\n\n    if s in ['', None]:\n        return ''\n\n    s1 = encodePassword(s)\n\n    if len(s1) &lt; 32:\n        sRet += dummyString\n\n    sRet += s1[::-1]  # reverse string\n\n    if len(sRet) &lt; 32:\n        sRet += dummyString\n\n    ch = getRandomValidCharFromList()\n    k = ord(ch) + len(sRet) - 2\n    sRet = ch + sRet\n\n    for i in range(1,len(sRet)):\n        j = findCharInList(sRet[i])\n        if j == -1:\n            return sRet\n        n = (j + k * (i+1)) % len(validCharList)\n        sRet = sRet[:i] + validCharList[n] + sRet[i+1:]\n\n    sRet += chr((ord(getRandomValidCharFromList())) + 2)\n    sRet = URLEncode(sRet)\n    return sRet\n\nfor i in range(1,len(sys.argv)):\n    p=sys.argv[i]\n    print scrambleString(p)<\/code><\/pre>\n\n<h1>\u00a0UNSCRAMBLING ALGORITHM<\/h1>\nThis algorithm shows how to unscramble the output from the previous script to regenerate the original password.\n\n<pre class=\"wp-block-code language-python\"><code class=\"language-python\">#!\/usr\/bin\/env python\n# ================================================================\n# Author : Joe Linoff\n# Date : 2012-02-04\n# Version: 1.0\n#\n# Unscrambles passwords creating by the NX scrambling algorithm.\n#\n# http:\/\/www.nomachine.com\/ar\/view.php?ar_id=AR01C00125\n#\n# Usage:\n#    % .\/nxpasswd.py \n#    &lt;scramble&gt;\n#    % .\/nxdecode.py &lt;scramble&gt;\n#    \n# ================================================================\nimport sys\nimport re\n\n# We know that the first character is always a ':'\n# We can use that to figure out the correct k value.\nvalidCharList = [\n         \"!\", \"#\", \"$\", \"%\", \"&amp;\", \"(\", \")\", \"*\", \"+\", \"-\",\n         \".\", \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\",\n         \"9\", \":\", \";\", \"&lt;\", \"&gt;\", \"?\", \"@\", \"A\", \"B\", \"C\",\n         \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\", \"K\", \"L\", \"M\",\n         \"N\", \"O\", \"P\", \"Q\", \"R\", \"S\", \"T\", \"U\", \"V\", \"W\",\n         \"X\", \"Y\", \"Z\", \"[\", \"]\", \"_\", \"a\", \"b\", \"c\", \"d\",\n         \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\",\n         \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\",\n         \"y\", \"z\", \"{\", \"|\", \"}\"\n         ]\n\ndef findCharInList(c):\n    global validCharList\n    for i in range(len(validCharList)):\n        if validCharList[i] == c:\n            return i\n    return -1\n\ndef findK(ch0,i=5,chf=':'):\n    global validCharList\n    j = findCharInList(chf)\n    for k in range(0,200):\n        n = (j + k * (i+1)) % len(validCharList)\n        m = validCharList[n]\n        if m == ch0:\n            return k\n    return -1\n\ndef unScramble(scramble):\n    scramble=URLDecode(scramble)\n\n    # Were dummy strings appended\/prepended?\n    # Need 3 tests to check.\n    tests = []\n    scramble=scramble[1:]\n    scramble=scramble[:-1]\n    tests.append([scramble,0])\n\n    scramble=scramble[4:]\n    tests.append([scramble,4])\n\n    scramble=scramble[:-4]\n    tests.append([scramble,4])\n\n    for data in tests:\n        scramble=data[0]\n        i=data[1]\n        k=findK(scramble[0],i+1)\n\n        charset=''\n        for ch in scramble:\n            i += 1\n            n = findCharInList(ch)\n            for j in range(len(validCharList)):\n                zn = (j + k * (i+1)) % len(validCharList)\n                if n == zn:\n                    ich = validCharList[j]\n                    charset += ich\n                    break\n\n        charset = charset[::-1]\n        ordvals = charset.split(':')\n        passwd = ''\n        i=0\n        ok = True\n        for ordval in ordvals:\n            if len(ordval)&gt;0:\n                m = re.search('^[0-9]+$',ordval)\n                if m:\n                    j = int(ordval)-i-1\n                    passwd += chr(j)\n                    i += 1\n                else:\n                    ok = False\n                    break\n\n        if ok:\n            return passwd\n\n    return None\n\ndef URLDecode(url):\n    url = url.replace('&amp;','&amp;')\n    url = url.replace('\"','\"')\n    url = url.replace(''',\"'\")\n    url = url.replace('&lt;','&lt;')\n    url = url.replace('&gt;','&gt;')\n    return url\n\nfor i in range(1,len(sys.argv)):\n    p=sys.argv[i]\n    print unScramble(p)<\/code><\/pre>\n\n<h1>EXAMPLE RUNS<\/h1>\nBelow I have run both programs to show they work using this test script.\n\n\n<pre class=\"wp-block-code language-bash\"><code class=\"language-bash\">#!\/bin\/bash\nps=(\n    'a'\n    '1'\n    '12'\n    '123'\n    '1234'\n    '12345'\n    '123456'\n    '1234567'\n    '12345678'\n    '123456789'\n    '1234567890'\n    '1234567890a'\n    '11111111111'\n    '1234567890ab' )\npassed=0\nfailed=0\ntotal=0\nfor p in ${ps[@]} ; do\n    (( total++ ))\n    echo\n    echo \"# ================================================\"\n    echo \"# test       : $total\"\n    echo \"# password   : $p\"\n    s=$(eval .\/nxpasswd.py \"'$p'\" | tail -1)\n    echo \"# scrambled  : $s\"\n    px=$(eval .\/nxdecode.py \"'$s'\" | tail -1)\n    echo \"# unscrambled: $px\"\n    if [[ \"$px\" != \"$p\" ]] ; then\n        echo \"# status     : FAILED\"\n        (( failed++ ))\n    else\n        echo \"# status     : OK\"\n        (( passed++ ))\n    fi\ndone\necho\necho \"# SUMMARY\"\necho \"#   Passed : $passed\"\necho \"#   Failed : $failed\"\necho \"#   Total  : $total\"<\/code><\/pre>\n\n\n\n\n<pre class=\"wp-block-code language-bash\"><code class=\"language-bash\"># ================================================\n# test       : 1\n# password   : a\n# scrambled  : _Kbv1as.EAUl$a\n# unscrambled: a\n# status     : OK\n\n# ================================================\n# test       : 2\n# password   : 1\n# scrambled  : _Kbv1ak)EAUl$a\n# unscrambled: 1\n# status     : OK\n\n# ================================================\n# test       : 3\n# password   : 12\n# scrambled  : aUq1K#7Tu)Jkl*Fbc\n# unscrambled: 12\n# status     : OK\n\n# ================================================\n# test       : 4\n# password   : 123\n# scrambled  : b_}Bb?VwAVyCV{EJj-Ld\n# unscrambled: 123\n# status     : OK\n\n# ================================================\n# test       : 5\n# password   : 1234\n# scrambled  : bf+NqQp6]yBj&amp;Mu2X$.Ps:d\n# unscrambled: 1234\n# status     : OK\n\n# ================================================\n# test       : 6\n# password   : 12345\n# scrambled  : cn9a*l5W(KqBc.YxGs4a1&gt;e0Ue\n# unscrambled: 12345\n# status     : OK\n\n# ================================================\n# test       : 7\n# password   : 123456\n# scrambled  : ctCm;!CsFn9i3Z0R}QsEt8h;KuDne\n# unscrambled: 123456\n# status     : OK\n\n# ================================================\n# test       : 8\n# password   : 1234567\n# scrambled  : d|O}P&gt;d:m5jAm&lt;p?lDl@s&gt;oGkCv0]1_f\n# unscrambled: 1234567\n# status     : OK\n\n# ================================================\n# test       : 9\n# password   : 12345678\n# scrambled  : d&amp;X0bP{S.V2h3kEtG}O#Z*]9c;r&gt;tOh&gt;oEf\n# unscrambled: 12345678\n# status     : OK\n\n# ================================================\n# test       : 10\n# password   : 123456789\n# scrambled  : !L5xbdG0wXD1lWE}mX@#nQ9%eM;vcO-ve#\n# unscrambled: 123456789\n# status     : OK\n\n# ================================================\n# test       : 11\n# password   : 1234567890\n# scrambled  : !R?)qvaH9{hV&lt;)uWH8tgU@&amp;tZF7weT8%sSE6#\n# unscrambled: 1234567890\n# status     : OK\n\n# ================================================\n# test       : 12\n# password   : 1234567890a\n# scrambled  : #q[B1&amp;oXL6!qWH:zm_B6&amp;oXL6}qWG:zl_B5&amp;%\n# unscrambled: 1234567890a\n# status     : OK\n\n# ================================================\n# test       : 13\n# password   : 11111111111\n# scrambled  : #oPD5{eUA)wbK&gt;$m_F3#gSF*uhK;-l[N1}p%\n# unscrambled: 11111111111\n# status     : OK\n\n# ================================================\n# test       : 14\n# password   : 1234567890ab\n# scrambled  : ${eWKH9!spbPI8*}kaVB91tnfUF?-zsaSL7-$jcY&amp;\n# unscrambled: 1234567890ab\n# status     : OK\n\n# SUMMARY\n#   Passed : 14\n#   Failed : 0\n#   Total  : 14<\/code><\/pre>\n\n\nEnjoy!\n","protected":false},"excerpt":{"rendered":"<p>The NX web page entitled &#8220;The password scrambling algorithm in NX client&#8221; 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&#8217;t use &hellip; <a href=\"https:\/\/joelinoff.com\/blog\/?p=156\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">NX password scrambling and unscrambling algorithms in python 2.7<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[5,7],"tags":[],"class_list":["post-156","post","type-post","status-publish","format-standard","hentry","category-programming","category-python"],"_links":{"self":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/156","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=156"}],"version-history":[{"count":21,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/156\/revisions"}],"predecessor-version":[{"id":1804,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/156\/revisions\/1804"}],"wp:attachment":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}