While trying to make a GET request from inside python3 on my Macbook I ran into this:

error: ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:600)

After doing a lot of reading of incomplete or incorrect blogs, this is how I fixed it.

Problem

I was writing some python code that made a GET request using the popular requests framework.  When I tried to run it I was given a strange error:

error: ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:600)

I’ve used requests with success before so this seemed odd.  I wrote the following quick python script to see if I could reproduce the error:

import requests

def main():
    print("Starting...")
    r = requests.get("https://jsonplaceholder.typicode.com/posts/1")
    print("Response: {c} Text: {t}".format(c=r.status_code, t=r.text))
    print("Done.")

if __name__ == '__main__':
    main()

To run this I brought up a virtual environment (running python 3.5) and did a:

(testvenv)$ pip install requests
(testvenv)$ python test.py
... stack trace ...
error: ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:600)

The problem was confirmed and repeatable. After doing some reading I discovered that Mac OSX comes preloaded with a fairly old version of open ssl.  I confirmed this by:

$ openssl version
OpenSSL 0.9.8zg 14 July 2015

According to the openssl website they recommend LTS version 1.0.2.  0.9.8 is, indeed, out of date.

Solution

Reading more about this lead me to a blog post on comeroutewithme.com which had a possible solution. So I followed along:

$ brew update
...
$ brew install openssl
...
$ openssl version
OpenSSL 0.9.8zg 14 July 2015

$ brew link --force openssl
Warning: Refusing to link: openssl
Linking key-only openssl means you may end up linking against the insecure,
deprecated system OpenSSL while using the headers from Homebrew's openssl.
Instead, pass the full include/library paths to your compiler e.g.:
–I/usr/local/opt/openssl/inc–/usr/local/opt/openssl/lib

So much for that! Some more reading lead me to @zlwaterfield’s  medium post  which claimed I needed to manually link the new version of ssl. However @zlwaterfield tells us to symlink to: /usr/local/Cellar/{version}/include/openssl which is incorrect, the correct location is: /usr/local/Cellar/{version}/bin/openssl. So I tried to update my openssl symlink via:

 $ sudo ln -s -f /usr/local/Cellar/{version}/bin/openssl /usr/bin/openssl 

the -f flag should overwrite the current symlink but it did’t for me so:

$ unlink /usr/bin/openssl
$ sudo ln -s /usr/local/Cellar/{version}/bin/openssl /usr/bin/openssl 
$ openssl version
OpenSSL 1.0.2e 3 Dec 2015

Great! openssl is now the correct version, but when check the python version of ssl I get:

$ python3
Python 3.5.1 (v3.5.1:37a07cee5969, Dec 5 2015, 21:12:44)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>import ssl
>>>ssl.OPENSSL_VERSION
'OpenSSL 0.9.8zg 14 July 2015'

Not cool! But now I can continue on from the first blog:

$ brew install python --with-brewed-openssl

but that updates it for python 2.7 not python 3… testing this confirmed that my python ssl version was still old. So:

$ brew install python3 --with-brewed-openssl

This produced a warning that brew did not want to overwrite my symlinks, so:

$ brew link --overwrite python3

This updated my python version from 3.5 to 3.6 so I had to update my path if I want the python3 command to point to 3.6:

PATH="/usr/local/Cellar/python3/3.6.3/bin/:${PATH}"
export PATH

Now the python3 command points to python 3.6:

$ python3
Python 3.6.3 (default, Oct 27 2017, 10:40:32) 
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ssl
>>> ssl.OPENSSL_VERSION
'OpenSSL 1.0.2l 25 May 2017'

Nice!! Now all I needed to do was rebuild my virtual environment around my test file and:

(newtestvenv)$ pip install requests
(newtestvenv)$ python test.py
Starting...
Response: 200 Text: {
 "userId": 1,
 "id": 1,
 "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
 "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
Done.

Success!  This took far too long and I got no proper work done this morning, but I shouldn’t run into this problem ever again.  Hopefully, this blog will help someone else get through this faster.