1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# - FTP/FTPS/SFTPクライアント(+ Ubuntu 24.04 でサーバ構築)

Last updated at Posted at 2025-03-16

はじめに

昔は、ファイル転送として、大変お世話になった FTP ですが、WEB API で事足りるので、しばらく利用する機会がありませんでした。
しかし、数年前、案件対応として、FTPS でのファイル転送を C# で実装するケースがありました。
今回は、FTP / FTPS / SFTP クライアントについて記載したいと思います。

テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。

  • Windows Forms - .NET Framework 4.8
  • Windows Forms - .NET 8
  • WPF - .NET Framework 4.8
  • WPF - .NET 8

Visual Studio 2022 - .NET Framework 4.8 は、C# 7.3 が既定です。
このため、サンプルコードは、C# 7.3 機能範囲で記述しています。

FTP / FTPS / SFTP サーバは、Ubuntu 24.04 上に構築しました。

こちらは主題ではないので、ペアでの確認用に、メモ程度に残しておきます。
と言いながら、こちらの情報量のほうが多いかも、、、
※テスト用の設定なので、実運用としては、相応しくない可能性があります。

FTP/FTPS/SFTP

FTP、FTPS、SFTP、SCP:ファイル転送プロトコルを解説

まずは、FTP / FTPS / SFTP の概要説明をします。

  • FTP
    • FTP(File Transfer Protocol)は、1970年代から利用されている、ファイル転送プロトコルで、制御チャネルとデータ転送チャネルの 2つのチャネル(デュアルチャネル)を利用します。
    • FTP には、Active(アクティブ:能動的)モードと Passive(パッシブ:受動的)モードが存在します。双方ともに、最初は、クライアントからサーバのポート番号 21 に接続して制御チャネルを確立します。
      • Active モードでは、クライアントからサーバに接続ポート番号を伝えて、サーバがポート番号 20 からクライアントの対象ポートに接続して、データチャネルを確立します。
      • Passive モードでは、サーバからクライアントに、サーバで割り当てらた範囲内のポート番号を伝えて、クライアントからサーバの対象ポートに接続して、データ転送チャネルを確立します。
    • メリット
      • 迅速、かつ、シンプルです。
    • デメリット
      • 暗号化されないため、セキュリティリスクがあります。
  • FTPS
    • FTPS(FTP Secure、もしくは、FTP over SSL/TLS)は、FTP で送受信するデータを、SSL/TLS で暗号化する通信プロトコルで、FTP と同様に、制御チャネルとデータ転送チャネルの 2つのチャネルを利用します。(Active モードと Passive モードが存在)
    • FTPS には、暗号化モードとして、Explicit(エクスプリシット:明示的)モード と Implicit(インプリシット:暗黙的)モードが存在します。
      • Explicit モードは、クライアントからサーバのポート番号 21 に接続して制御チャネルを確立します。通信開始時は暗号化せず、クライアントが AUTH コマンドを実行することで、途中から通信内容を暗号化します。データ転送チャネルの利用ポート番号については、Active モード / Passive モードともに FTP と同様です。
      • Implicit モードは、クライアントからサーバのポート番号 990 に接続して制御チャネルを確立して、初めから暗号化通信を行います。データ転送チャネルの利用ポート番号については、Active モードの場合、サーバはポート番号 20 ではなく 989 を利用します。Passive モードの場合、FTP と同様です。
    • メリット
      • 既存の FTP インフラを活用しながらセキュリティを強化できるため、システムの大幅な変更が不要です。
      • SSL/TLS で、認証情報、および、転送データの暗号化を行うことで、セキュリティリスクが軽減されます。
    • デメリット
      • ファイアウォールに妨げられることがあるので、ポート番号、もしくは、FTPS サーバ / クライアントソフトの例外登録が必要となります。
  • SFTP
    • SFTP(SSH File Transfer Protocol)は、SSH(Secure Shell)を利用したファイル転送プロトコルです。SSH の暗号化を利用することで、高いセキュリティを確保します。また、FTP とは異なり、シングルチャネル(制御とデータ転送を同じチャネルで実施)実装となり、ポート番号 22 を利用します。
    • メリット
      • すべての通信が暗号化され、認証も SSH の強力な仕組みに基づいて行われます。
      • シングルチャネルのため、設定やファイアウォールの構成が容易です。
    • デメリット
      • SSH が標準利用できる環境でない場合には、各種ソフトの導入が必要となります。

上記に記載したポート番号は一般的なポート番号です。
システムによっては、異なるポート番号を利用するケースもあります。

上記以外に、SSH を使用したファイル転送プロトコル SCP(Secure Copy Protocol)もありますが、今回は割愛させて頂きます。

事前準備

Ubuntu

下記目的で ftpusers というグループを作成します。

  • FTP / FTPS で vsftpd.conf - umask を 002 として、対象グループで更新可とする
  • SFTP 接続を対象グループで許可して umask を 002 として、対象グループで更新可とする
$ sudo groupadd ftpusers

ftpuser1, ftpuser2 というユーザを ftpusers グループで作成して、パスワードを設定します。

$ sudo useradd -m -g ftpusers ftpuser1
$ sudo passwd sftpuser1
新しい パスワード: 
新しい パスワードを再入力してください: 
passwd: パスワードは正しく更新されました
$ sudo useradd -m -g ftpusers ftpuser2
$ sudo passwd sftpuser2
新しい パスワード: 
新しい パスワードを再入力してください: 
passwd: パスワードは正しく更新されました

共用公開ディレクトリとして /var/ftproot を作成して、サンプルとして hoge1.txt を配置します。

$ sudo -i
# mkdir /var/ftproot
# chown root:ftpusers /var/ftproot
# chmod 0775 /var/ftproot
# echo "HogeHoge" > /var/ftproot/hoge1.txt
# chown ftpuser2:ftpusers /var/ftproot/hoge1.txt
# chmod 0664 /var/ftproot/hoge1.txt
$

C# クライアント

FTP / FTPS は、FtpWebRequest を用いた実装が可能でしたが、FtpWebRequest 基底クラス WebRequest が .NET 6 で非推奨となり、FtpWebRequest マニュアルに「新しい開発に FtpWebRequest を使用することはお勧めしません」との記載がされました。
このため、NuGet Gallery | FluentFTP を利用することとします。

SFTP については、NuGet Gallery | SSH.NET を利用します。

PM> NuGet\Install-Package FluentFTP
PM> NuGet\Install-Package SSH.NET

FTP

FTP - Passive をサンプルとします。

Ubuntu

FTPサーバ vsftpdの設定(vsftpd.conf)

vsftpd(3.0.5)をインストールして、起動されていることを確認します。

$ sudo apt update
$ sudo apt install vsftpd

$ sudo systemctl status vsftpd

vsftpd の設定ファイルで、接続時のルートディレクトリ、Passive モードなどを設定します。

$ sudo vi /etc/vsftpd.conf
# vsftpd.conf 既存項目
listen=YES
listen_ipv6=NO
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=002
use_localtime=YES
connect_from_port_20=NO
ascii_upload_enable=YES
ascii_donwload_enable=YES
chroot_local_user=YES

# 接続可能ユーザ制限
userlist_enable=YES
userlist_deny=NO
userlist_file=/etc/vsftpd.user_list

# 接続時のルートディレクトリ
local_root=/var/ftproot

# 書き込み権限があるとchroot出来ない機能を無効
allow_writeable_chroot=YES

# Passive モード関係
pasv_enable=YES
pasv_min_port=50000
pasv_max_port=50050

接続可能ユーザを vsftpd.user_list に記載します。

$ sudo vi /etc/vsftpd.user_list
ftpuser1
ftpuser2

FireWall の状態を確認、非アクティブでしたので FireWall 設定はスキップします。
※本記事は Ubuntu サーバ構築ではなく、FTP/FTPS/SFTP クライアントが主題のため。

$ sudo ufw status
状態:非アクティブ

vsftpd を再起動します。

$ sudo systemctl restart vsftpd

C# クライアント

FluentFTP は GitHub で公開されていて、CSharpExamples もあります。

以前、利用した時は 37.0 でしたが、RELEASES.md を見ると、40.0 で大幅な改修がされて、設定情報が Config に集約されたようです。

FtpClient(同期)、AsyncFtpClient(非同期)が用意されています。
FtpClient での 接続 / 一覧取得 / ダウンロード / アップロード / 切断 サンプルコードを記載します。

using FluentFTP;
// FtpClient は、IDisposable なので、using もしくは Dispose() が必要
// IP_OR_HOST:接続先、ftpuser1:ユーザ名、PASSWORD:パスワード、21:接続ポート
using (var client = new FluentFTP.FtpClient("IP_OR_HOST", "ftpuser1", "PASSWORD", 21))
{
  // FTP - Passive
  client.Config.EncryptionMode = FtpEncryptionMode.None;
  client.Config.DataConnectionType = FtpDataConnectionType.AutoPassive;

  // タイムアウト値 (msec)
  client.Config.ConnectTimeout = 5000;
  // 必要に応じて下記も設定
  // client.Config.DataConnectionConnectTimeout
  // client.Config.DataConnectionReadTimeout

  // 接続
  try
  {
    client.Connect();
  }
  catch (System.TimeoutException)
  {
    // タイムアウト - TODO
  }
  catch (FluentFTP.Exceptions.FtpAuthenticationException)
  {
    // 認証情報不正、および、FTP接続許可されていないユーザ - TODO
  }

  // 公開フォルダの一覧取得
  FtpListItem[] items = client.GetListing();
  foreach (var item in items)
  {
    var target = item.Name;
    // TODO
  }

  // ダウンロード
  string localPath = System.IO.Path.GetTempPath() + "hoge1.txt";
  string remotePath = "hoge1.txt";
  FtpStatus sts = client.DownloadFile(localPath, remotePath, FtpLocalExists.Overwrite);
  if (sts != FtpStatus.Success)
  {
    // ERROR - TODO
  }

  // アップロード
  remotePath = "hoge2.txt";
  sts = client.UploadFile(localPath, remotePath, FtpRemoteExists.Overwrite);
  if (sts != FtpStatus.Success)
  {
    // ERROR - TODO
  }

  // 切断
  client.Disconnect();
}

FluentFTP.FtpClient、上記以外の主な機能を記載します。

メソッド 内容
FileExists リモートファイル存在確認
DeleteFile リモートファイル削除
DirectoryExists リモートディレクトリ存在確認
CreateDirectory リモートディレクトリ作成
Rename リモートファイル名称変更

FTPS

FTPS - Implicit Passive をサンプルとします。

Ubuntu

【vsftpd】FTPをSSL/TLSで暗号化する設定 #ftps - Qiita
vsftpdでchrootを使って複数ユーザでFTPS接続させる
vsftpd.conf(5) — Arch manual pages

前述、FTP サーバとして構築した環境を FTPS - Implicit に再構築します。
まず、自己署名証明書を作成します。
vsftpd.key 作成で、画面表示に従いパスワードを設定します。

$ sudo -i
# cd /etc/ssl/private
# openssl genrsa -aes256 4096 > vsftpd.key
Generating RSA private key, 4096 bit long modulus (2 primes)
........++++
.......................++++
e is 65537 (0x010001)
Enter pass phrase:
Verifying - Enter pass phrase:

vsftpd.conf では、rsa_private_key_file に対するパスワード設定項目がないので、パスワードを削除した後、vsftpd.pem を作成して、ファイルモードを変更します。

# openssl rsa -in vsftpd.key -out vsftpd.key
Enter pass phrase for vsftpd.key:
writing RSA key
# openssl req -new -key vsftpd.key -x509 -days 365 -out vsftpd.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) []:Tokyo
Organization Name (eg, company) [Internet Widgits Pty Ltd]:hoge
Organizational Unit Name (eg, section) []:hoge
Common Name (e.g. server FQDN or YOUR name) []:hoge
Email Address []:hoge@example.com
# chmod 0666 vsftpd.key vsftpd.pem
# exit
$

自己署名証明書なので、 vsftpd.key と vsftpd.pem から、Windows 用の vsftpd.pfx 作成は割愛させて頂きます。

vsftpd の設定ファイルに下記内容を更新します。

$ sudo vi /etc/vsftpd.conf
# vsftpd.conf 既存項目をコメントアウト
#rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
#rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
#ssl_enable=NO

# SSL/TLSを有効化
ssl_enable=YES

# 使用プロトコル
ssl_sslv2=NO
ssl_sslv3=NO
ssl_tlsv1=NO
ssl_tlsv11=NO
ssl_tlsv12=YES
ssl_tlsv13=YES

# 暗号化方式
ssl_ciphers=HIGH

# サーバ証明書、秘密鍵
rsa_cert_file=/etc/ssl/private/vsftpd.pem
rsa_private_key_file=/etc/ssl/private/vsftpd.key

# Implicit モード設定
implicit_ssl=YES
listen_port=990

vsftpd.conf 記載項目でつまずいたら、Ubuntu – Ubuntu パッケージ検索 で vsftpd を検索、vsftpd_3.0.5.orig.tar.gz をダウンロード、解凍した parseconf.c を確認という手もあります。

vsftpd を再起動します。

$ sudo systemctl restart vsftpd

C# クライアント

FTP サンプルコードの一部を更新して、FTPS - Implicit Passive とします。

  • ポート番号 990 利用
  • FtpEncryptionMode.Implicit 指定
  • ValidateCertificate コールバック
// FtpClient は、IDisposable なので、using もしくは Dispose() が必要
// IP_OR_HOST:接続先、ftpuser1:ユーザ名、PASSWORD:パスワード、990:接続ポート
using (var client = new FluentFTP.FtpClient("IP_OR_HOST", "ftpuser1", "PASSWORD", 990))
{
  // FTPS - Implicit Passive
  client.Config.EncryptionMode = FtpEncryptionMode.Implicit;
  client.Config.DataConnectionType = FtpDataConnectionType.AutoPassive;

  // 証明書の内容を確認しない(自己署名証明書なので)
  client.ValidateCertificate += (control, e) => { e.Accept = true; };

  // タイムアウト値 (msec)
  client.Config.ConnectTimeout = 5000;
  
<以降は FTP と同一>

正規の認証局が発行する証明書がある場合には、下記手順で証明書を利用します。

// 証明書
string pfxPath = "PATH_TO_PFX";                              // TODO - pfxファイルのパス
var certificate = new X509Certificate2(pfxPath, "PASSWORD"); // TODO - pfxのパスワード
client.Config.ClientCertificates.Add(certificate);
client.ValidateCertificate += (control, e) => {
  if (e.PolicyErrors == System.Net.Security.SslPolicyErrors.None)
  {
    e.Accept = true;
  }
  else
  {
    // TODO 
    e.Accept = false;
  }
};

SFTP

パスワード認証と公開鍵認証がありますが、今回は、パスワード認証とします。

Ubuntu

【Ubuntu】Linuxでsftpサーバー構築し、sftpユーザーのディレクトリ制限を行う方法

SSHサーバとしてOpen SSH Serverをインストールして、起動されていることを確認します。

$ sudo apt update
$ sudo apt install openssh-server

$ sudo systemctl status ssh

sshd_config を修正します。

$ sudo vi /etc/ssh/sshd_config
# ssh 無効、sftp のみ有効
#Subsystem      sftp    /usr/lib/openssh/sftp-server
Subsystem       sftp    internal-sftp

# パスワード認証無効、公開鍵認証有効
PasswordAuthentication no
PubkeyAuthentication yes

# ftpusers グループはパスワード認証有効、/var/ftproot を初期ディレクトリ
Match Group ftpusers
        X11Forwarding no
        AllowTcpForwarding no
        PasswordAuthentication yes
        ForceCommand internal-sftp -d /var/ftproot -u 002

ForceCommand internal-sftp 引数は、sftp-server(8) - Linux manual page 参照してください。

FireWall の状態を確認、非アクティブでしたので FireWall 設定はスキップします。
※本記事は Ubuntu サーバ構築ではなく、FTP/FTPS/SFTP クライアントが主題のため。

$ sudo ufw status
状態:非アクティブ

SSHサーバを再起動します。

$ sudo systemctl restart ssh

C# クライアント

GitHub - sshnet/SSH.NET: SSH.NET is a Secure Shel
C#でSFTPでファイル転送やってみる

SftpClient での 接続 / 一覧取得 / ダウンロード / アップロード / 切断 サンプルコードを記載します。

// SftpClient は、IDisposable なので、using もしくは Dispose() が必要
// IP_OR_HOST:接続先、22:接続ポート、ftpuser1:ユーザ名、PASSWORD:パスワード
using (var client = new SftpClient("IP_OR_HOST", 22, "ftpuser1", "PASSWORD"))
{
  // タイムアウト値 (TimeSpan)
  client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(5);
  // 必要に応じて下記も設定
  // client.OperationTimeout

  // 接続
  try
  {
    client.Connect();
  }
  catch (Renci.SshNet.Common.SshOperationTimeoutException)
  {
    // タイムアウト - TODO
  }
  catch(Renci.SshNet.Common.SshAuthenticationException)
  {
    // 認証情報不正、および、対象ユーザはパスワード認証無効 - TODO
  }

  // 公開フォルダの一覧取得
  foreach (ISftpFile file in client.ListDirectory("."))
  {
    var name = file.Name;
    if (name.StartsWith("."))
    {
      // "."、".." などをスキップ
      continue;
    }
    // TODO
    var size = file.Length;
  }

  // ダウンロード
  string localPath = System.IO.Path.GetTempPath() + "hoge1.txt";
  string remotePath = "hoge1.txt";
  using (var fs = System.IO.File.OpenWrite(localPath))
  {
    client.DownloadFile(remotePath, fs);
  }

  // アップロード
  remotePath = "hoge2.txt";
  using (var fs = System.IO.File.OpenRead(localPath))
  {
    client.UploadFile(fs, remotePath, true);  // true:上書き
  }

  // 切断
  client.Disconnect();
}

SftpClient、および、ISftpFile、上記以外の主な機能を記載します。

メソッド 内容
SftpClient.CreateDirectory ディレクトリ作成
SftpClient.Exist 存在確認
ISftpFile.Delete 削除
ISftpFile.MoveTo リネーム

対象がディレクトリかの確認は、プロパティで可能です。

プロパティ 内容
ISftpFile.IsDirectory ディレクトリ確認
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?