Generate SSH Keypairs (private/public) without ssh-keygen
Turns out this was much more complicated than I anticipated. I ended up writing the SSHKey gem to pull it off (source code on GitHub). SSH Public keys are encoded totally differently from the RSA public key provided. Data type encoding for SSH keys are defined in section #5 of RFC #4251.
How using ssh-keygen generate public key that would have ---- BEGIN SSH2 PUBLIC KEY ---- at the top?
Figured out myself
I had to add -e
(export) option
Full command looks following:
ssh-keygen -b 2048 -C "email@gmail.com" -f $HOME/.ssh/id -e
ssh-keygen and openssl gives two different public keys
It's the same key but different representations. OpenSSL uses X.509 SubjectPublicKeyInfo in ASN.1, usually (including here) wrapped in PEM; OpenSSH (except 'rsa1' keys for SSHv1 which is broken and you shouldn't use) uses the XDR-like SSH wire format, in base64.
Dupe or neardupe:
Convert pem key to ssh-rsa format
RSA Public Key format
Convert RSA public key to RSA DER
Converting an OpenSSL generated RSA public key to OpenSSH format (PHP)
How to convert RSA key to ssh-rsa
How to store/retrieve RSA public/private key (buried in the middle)
and less obvious cross-stack https://security.stackexchange.com/questions/42268/how-do-i-get-the-rsa-bit-length-with-the-pubkey-and-openssl
What is the proper way to generate, store, and configure an SSH public/private key pair for git repository usage on Assembla?
Since you are just entering gitrep
, it's just saving it in your current directory (which is apparently your home directory, judging from your example above).
Check and see if ~/gitrep
and ~/gitrep.pub
exist. You'll need to copy the contents of the gitrep.pub
file to the destination when it asks you for your public key.
Run ssh-keygen in Ruby to generate VCS deploy keys?
Using Kernel Calls to OpenSSH Utilities
This is one of those things that in my opinion should not be ported directly to Ruby. While the OpenSSH binaries and source are routinely audited, lightly- or rarely-used Ruby gems or FFI wrappers wouldn't get the same level of scrutiny. Instead, you should use the Kernel#system or Kernel#` calls or the %x()
subshell literal, depending on your use case.
Calling External SSH Utilities from Inside Ruby
For example:
# Note that 3072 is currently the default size for RSA keys in
# OpenSSH. Also note that you can pass `-f path/to/keyfile` or
# `-P ""` for an empty passphrase if you don't use the `-A` flag.
system %(ssh-keygen -A -b 2048 -t rsa -C "mystring")
Without the -f
flag, your RSA keys will be placed into ~/.ssh/id_rsa
and ~/.ssh/id_rsa.pub
. You can then use standard Ruby methods for reading the files if you really need to, although again I can't think of many reasons why you'd need to do this.
For example, to read in your public key into a Ruby variable:
public_key = File.read "#{ENV['HOME']/.ssh/id_rsa.pub"
Using the SSH-Agent
Note that if you are already inside a running program, your biggest challenge will be using your SSH agent if you're using one, since the environment variables and key material are unlikely to be available at this point. However, you can start an agent and load your key file within your current session if you like. For example:
ssh_agent = %x(eval 'ssh-agent -s')
# assumes a passwordless key
system("ssh-add") && %x(ssh-add -l)
# Your agent's SSH_AUTH_SOCK is now exported by your current Ruby
# environment.
ENV['SSH_AUTH_SOCK']
# For some reason, SSH_AGENT_PID isn't exported properly. This may be
# user error on my part. Luckily, you can easily parse it out of the
# *ssh_agent* variable if needed.
ENV['SSH_AGENT_PID'] =
ssh_agent.match(/SSH_AGENT_PID=\d+/).to_s.split(?=).last
Security Note
If you're running on macOS or a Linux system with keychain installed, you're better off using a password stored in your login keychain or prompted for when starting ssh-agent. Passwordless keys have their place, but there are more-secure options that are just as easy to manage on most modern systems. YMMV based on your exact use case.
See Also
There's a net-ssh gem that also contains ssh-agent support. Whether or not this is suitable for your needs or sufficiently audited for your use case is up to you. However, other visitors who don't want to roll their own or call out to external utilities should be aware of this solution, and there are likely to be others as well. Again, your mileage may vary.
How to execute ssh-keygen without prompt
We need to accomplish two steps automatically:
Enter a passphrase. Use the
-N
flag (void string for this example):ssh-keygen -t rsa -N ''
Overwrite the key file:
Use -f
to enter the path (in this example id_rsa
) plus a here-string to answer yes to the following question:
ssh-keygen -q -t rsa -N '' -f ~/.ssh/id_rsa <<<y >/dev/null 2>&1
Or, under a bash
like shell, If you certainly want to overwrite the previous one, use just a here-string to feed the command with all the need input:
ssh-keygen -q -t rsa -N '' <<< $'\ny' >/dev/null 2>&1
From ssh-keygen
man page:
-N new_passphrase provides the new passphrase.
-q silence ssh-keygen.
-f filename specifies the filename of the key file.
Step by step explanation
$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/klashxx/.ssh/id_rsa):
1) To avoid entering the key use -f
:
$ ssh-keygen -t rsa -f ~/.ssh/id_rsa
Generating public/private rsa key pair.
/home/klashxx/.ssh/id_rsa already exists.
Overwrite (y/n)?
ATTENTION: If you don't care about the RSA file name and certainly want to overwrite the previous one, check the instructions below point four.
2) Now we need to answer "y" automatically to the overwrite question (let's use a here-string for that job):
$ ssh-keygen -t rsa -f ~/.ssh/id_rsa <<< y
Generating public/private rsa key pair.
/home/klashxx/.ssh/id_rsa already exists.
Overwrite (y/n)? Enter passphrase (empty for no passphrase):
3) Finally we're going to use the -N
flag to enter a void pass:
$ ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa <<< y
Generating public/private rsa key pair.
/home/klashxx/.ssh/id_rsa already exists.
Overwrite (y/n)? Your identification has been saved in /home/klashxx/.ssh/id_rsa.
Your public key has been saved in /home/klashxx/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:Xo0t6caMB/8TSsigxfY28JIfqYjyqxRZrFrPncx5yiU klashxx@server
The key's randomart image is:
+---[RSA 2048]----+
| |
| . |
| o . |
| + * = |
| +. + BSo= o |
|...o.+o+XO... |
|.. .o.E==+B. . |
|o . ...=.o... |
|.+o. o .. |
+----[SHA256]-----+
4) Extra ball, cleanup the output, just check the return code:
$ ssh-keygen -q -t rsa -N '' -f ~/.ssh/id_rsa <<<y >/dev/null 2>&1
$ echo $?
0
An alternative path to overwrite the previous RSA file (no -f flag needed)
NOTE: Only bash
like shells.
If you don't care about the RSA name and just want to overwrite it, we need to answer these two questions automatically:
Enter file in which to save the key: /example/path/.ssh/id_rsa already exists.
Overwrite (y/n)?
If we do this by hand, for the first question we just need to hit enter, and for the second, type y
and press enter
.
We can simulate these actions by using the following here-string:
$'\ny'
From the bash
man page:
Words of the form $'string' are treated specially. The word expands to
"string", with backslash-escaped characters replaced as specified by
the ANSI C standard.\n new line
So, if we use od
to analyze our string:
cat - <<< $'\ny' | od -c
0000000 \n y \n
We see that we're getting just what we need to answer the questions.
Points 1 and 2 can be summarized into:
ssh-keygen -q -t rsa <<< $'\ny'
And the final command will be:
$ ssh-keygen -q -t rsa -N '' <<< $'\ny' >/dev/null 2>&1
$ echo $?
0
Kudos
@lukasz-dynowski, @redochka, @mellow-yellow, @yeti and the rest of the folks in this thread.
Related Topics
Ruby Get Time in Given Timezone
How to Get a List of Files That Have Been 'Required' in Ruby
Verb-Agnostic Matching in Sinatra
How to Run Ruby 2.0 with Jruby 1.7
Concurrent Requests with Mri Ruby
What Does the Operator ||= Stand for in Ruby
CSV - Unquoted Fields Do Not Allow \R or \N (Line 2)
Is Alias_Method_Chain Synonymous with Alias_Method
How to Install 'Cocoapods' Gem from Rubygems.Org (Bad Response Backend Read Error)
Select Mailbox "Sent Mail" or "All Mail" in Ruby Net::Imap
Are There Better Ways to Prevent 'Yield' When No Block Is Passed In
Missing a Template for This Request Format and Variant
Ruby: How to Find Non-Unique Elements in Array and Print Each with Number of Occurrences
How to Use a Branch in a Fork of Rails in a Project with Bundler
Make: /Usr/Bin/Mkdir: Command Not Found During 'Gem Install Nokogiri' in Ubuntu 20.04
What's the Difference Between Ruby Core API and Standard Library API