Shouldn't this be handled by `substitute-command-keys' already? The fix
looks like a hack.

I don't think this has anything to do with substitute-command-keys. 
That function simply returns a string.

So the below 2 forms display the exact same thing: "C-c ’" (note the curved right quote) by default in emacs 25.0.92:

(with-temp-buffer
  (org-mode)
  (message (substitute-command-keys "\\[org-edit-special]")))

(message "C-c '")

So the way to get the straight quotes printed as they are without getting converted to curved quotes is by using

(message "%s" STRING)

Reference: https://lists.gnu.org/archive/html/bug-gnu-emacs/2015-10/msg00234.html
@Paul: Please correct me if that's wrong.

Now both of the below forms echo "C-c '" (with straight quote):

(with-temp-buffer
  (org-mode)
  (message "%s" (substitute-command-keys "\\[org-edit-special]")))

(message "%s" "C-c '")


Shouldn't ` and ' also be dropped around \\[command]?

It looks like this convention is used at many other places in org (and many mores places in emacs elisp source):

org-gnus.el:287:  "Like `\\[gnus]' but doesn't check for new news."
org-id.el:105:      a link to it, using `\\[org-insert-link]' first.
org-footnote.el:536: "Edit definition and go back with `\\[org-mark-ring-goto]' or, if \
org-footnote.el:537:unique, with `\\[org-ctrl-c-ctrl-c]'.")))
org-footnote.el:903: "\\`\\[fn:\\(.*?\\)\\]" new stored nil nil 1)))
org-lint.el:450:  (and h (org-string-match-p "\\`\\[.*\\]\\'" h)))
org-colview.el:524:  (if (string-match "\\`\\[[ xX-]\\]\\'"
org-src.el:737:       (progn (string-match (if inline "\\`\\[fn:.*?:" "\\`.*?\\]") c)
ox-beamer.el:210:  ((string-match "\\`\\[<.*>\\]\\'" argument) argument)
ox-beamer.el:213:  ((string-match "\\`\\[\\(.*\\)\\]\\'" argument)
ox-beamer.el:216:      (option (if (string-match "\\`\\[.*\\]\\'" argument) argument
ox-beamer.el:426:       ((string-match "\\`\\[.*\\]\\'" action )
ox-beamer.el:567:     ((string-match "\\`\\[.*\\]\\'" action)
ox-beamer.el:613: (if (string-match "\\`\\[.*\\]\\'" overlay) 'defaction
org-clock.el:104:`\\[org-clock-out]', or until the clock is started in a different item.
ox-latex.el:2274:    (if (not (string-match "\\`\\[\\(.*\\)\\]\\'" opt)) opt
org-table.el:3596:Edit formulas, finish with `\\[org-ctrl-c-ctrl-c]' or `\\[org-edit-special]'.  \
org-agenda.el:1302:`\\[universal-argument] \\[org-agenda-log-mode]' in
org-agenda.el:1989:\\<org-agenda-mode-map>`\\[org-agenda-filter-by-tag] RET'.
org-agenda.el:4697:Press `\\[org-agenda-manipulate-query-add]', \
org-agenda.el:4698:`\\[org-agenda-manipulate-query-subtract]' to add/sub word, \
org-agenda.el:4699:`\\[org-agenda-manipulate-query-add-re]', \
org-agenda.el:4700:`\\[org-agenda-manipulate-query-subtract-re]' to add/sub regexp, \
org-agenda.el:4701:`\\[universal-argument] \\[org-agenda-redo]' to edit\n"))
org-agenda.el:4897:   "Press `\\[universal-argument] \\[org-agenda-redo]' \
org-agenda.el:7320:So this is just a shortcut for \\<global-map>`\\[org-agenda]', available
org.el:868:`\\[org-todo]' and `\\[org-priority]' \
org.el:870:`\\[universal-argument] \\[universal-argument] \\[org-todo]' \
org.el:872:`\\[org-ctrl-c-minus]' to cycle item bullet types,
org.el:1670:When nil, `\\[org-ctrl-c-ctrl-c]' needs to be used \
org.el:1673:`\\[org-toggle-latex-fragment]' to be removed."
org.el:2431:`\\[universal-argument] \\[universal-argument] \\[universal-argument] \
org.el:2614:with a prefix argument,  i.e. `\\[universal-argument] \\[org-todo]' \
org.el:2616:`\\[universal-argument] t' in an agenda buffer.
org.el:3093:`\\[org-time-stamp]' or `\\[org-time-stamp-inactive],
org.el:3823:commands `org-search-view' (`\\[org-agenda] s') \

As this was a cosmetic thing, I decided to keep that unchanged. Personally I also prefer to not have any quotes around the key-bindings. The patch I submitted merely makes the displayed key-binding less confusing; we have a straight-quote key on the keyboard, not a curved quote key :)